checks.go 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123
  1. /*
  2. Copyright 2016 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package preflight
  14. import (
  15. "bufio"
  16. "bytes"
  17. "crypto/tls"
  18. "crypto/x509"
  19. "encoding/json"
  20. "fmt"
  21. "io"
  22. "io/ioutil"
  23. "net"
  24. "net/http"
  25. "net/url"
  26. "os"
  27. "path/filepath"
  28. "runtime"
  29. "strings"
  30. "time"
  31. "github.com/PuerkitoBio/purell"
  32. "github.com/blang/semver"
  33. "github.com/pkg/errors"
  34. netutil "k8s.io/apimachinery/pkg/util/net"
  35. "k8s.io/apimachinery/pkg/util/sets"
  36. versionutil "k8s.io/apimachinery/pkg/util/version"
  37. "k8s.io/klog"
  38. kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
  39. kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
  40. "k8s.io/kubernetes/cmd/kubeadm/app/images"
  41. utilruntime "k8s.io/kubernetes/cmd/kubeadm/app/util/runtime"
  42. "k8s.io/kubernetes/cmd/kubeadm/app/util/system"
  43. "k8s.io/kubernetes/pkg/master/ports"
  44. "k8s.io/kubernetes/pkg/registry/core/service/ipallocator"
  45. "k8s.io/kubernetes/pkg/util/initsystem"
  46. ipvsutil "k8s.io/kubernetes/pkg/util/ipvs"
  47. kubeadmversion "k8s.io/kubernetes/pkg/version"
  48. utilsexec "k8s.io/utils/exec"
  49. )
  50. const (
  51. bridgenf = "/proc/sys/net/bridge/bridge-nf-call-iptables"
  52. bridgenf6 = "/proc/sys/net/bridge/bridge-nf-call-ip6tables"
  53. ipv4Forward = "/proc/sys/net/ipv4/ip_forward"
  54. ipv6DefaultForwarding = "/proc/sys/net/ipv6/conf/default/forwarding"
  55. externalEtcdRequestTimeout = time.Duration(10 * time.Second)
  56. externalEtcdRequestRetries = 3
  57. externalEtcdRequestInterval = time.Duration(5 * time.Second)
  58. )
  59. var (
  60. minExternalEtcdVersion = semver.MustParse(kubeadmconstants.MinExternalEtcdVersion)
  61. )
  62. // Error defines struct for communicating error messages generated by preflight checks
  63. type Error struct {
  64. Msg string
  65. }
  66. // Error implements the standard error interface
  67. func (e *Error) Error() string {
  68. return fmt.Sprintf("[preflight] Some fatal errors occurred:\n%s%s", e.Msg, "[preflight] If you know what you are doing, you can make a check non-fatal with `--ignore-preflight-errors=...`")
  69. }
  70. // Preflight identifies this error as a preflight error
  71. func (e *Error) Preflight() bool {
  72. return true
  73. }
  74. // Checker validates the state of the system to ensure kubeadm will be
  75. // successful as often as possible.
  76. type Checker interface {
  77. Check() (warnings, errorList []error)
  78. Name() string
  79. }
  80. // ContainerRuntimeCheck verifies the container runtime.
  81. type ContainerRuntimeCheck struct {
  82. runtime utilruntime.ContainerRuntime
  83. }
  84. // Name returns label for RuntimeCheck.
  85. func (ContainerRuntimeCheck) Name() string {
  86. return "CRI"
  87. }
  88. // Check validates the container runtime
  89. func (crc ContainerRuntimeCheck) Check() (warnings, errorList []error) {
  90. klog.V(1).Infoln("validating the container runtime")
  91. if err := crc.runtime.IsRunning(); err != nil {
  92. errorList = append(errorList, err)
  93. }
  94. return warnings, errorList
  95. }
  96. // ServiceCheck verifies that the given service is enabled and active. If we do not
  97. // detect a supported init system however, all checks are skipped and a warning is
  98. // returned.
  99. type ServiceCheck struct {
  100. Service string
  101. CheckIfActive bool
  102. Label string
  103. }
  104. // Name returns label for ServiceCheck. If not provided, will return based on the service parameter
  105. func (sc ServiceCheck) Name() string {
  106. if sc.Label != "" {
  107. return sc.Label
  108. }
  109. return fmt.Sprintf("Service-%s", strings.Title(sc.Service))
  110. }
  111. // Check validates if the service is enabled and active.
  112. func (sc ServiceCheck) Check() (warnings, errorList []error) {
  113. klog.V(1).Infoln("validating if the service is enabled and active")
  114. initSystem, err := initsystem.GetInitSystem()
  115. if err != nil {
  116. return []error{err}, nil
  117. }
  118. warnings = []error{}
  119. if !initSystem.ServiceExists(sc.Service) {
  120. warnings = append(warnings, errors.Errorf("%s service does not exist", sc.Service))
  121. return warnings, nil
  122. }
  123. if !initSystem.ServiceIsEnabled(sc.Service) {
  124. warnings = append(warnings,
  125. errors.Errorf("%s service is not enabled, please run '%s'",
  126. sc.Service, initSystem.EnableCommand(sc.Service)))
  127. }
  128. if sc.CheckIfActive && !initSystem.ServiceIsActive(sc.Service) {
  129. errorList = append(errorList,
  130. errors.Errorf("%s service is not active, please run 'systemctl start %s.service'",
  131. sc.Service, sc.Service))
  132. }
  133. return warnings, errorList
  134. }
  135. // FirewalldCheck checks if firewalld is enabled or active. If it is, warn the user that there may be problems
  136. // if no actions are taken.
  137. type FirewalldCheck struct {
  138. ports []int
  139. }
  140. // Name returns label for FirewalldCheck.
  141. func (FirewalldCheck) Name() string {
  142. return "Firewalld"
  143. }
  144. // Check validates if the firewall is enabled and active.
  145. func (fc FirewalldCheck) Check() (warnings, errorList []error) {
  146. klog.V(1).Infoln("validating if the firewall is enabled and active")
  147. initSystem, err := initsystem.GetInitSystem()
  148. if err != nil {
  149. return []error{err}, nil
  150. }
  151. warnings = []error{}
  152. if !initSystem.ServiceExists("firewalld") {
  153. return nil, nil
  154. }
  155. if initSystem.ServiceIsActive("firewalld") {
  156. warnings = append(warnings,
  157. errors.Errorf("firewalld is active, please ensure ports %v are open or your cluster may not function correctly",
  158. fc.ports))
  159. }
  160. return warnings, errorList
  161. }
  162. // PortOpenCheck ensures the given port is available for use.
  163. type PortOpenCheck struct {
  164. port int
  165. label string
  166. }
  167. // Name returns name for PortOpenCheck. If not known, will return "PortXXXX" based on port number
  168. func (poc PortOpenCheck) Name() string {
  169. if poc.label != "" {
  170. return poc.label
  171. }
  172. return fmt.Sprintf("Port-%d", poc.port)
  173. }
  174. // Check validates if the particular port is available.
  175. func (poc PortOpenCheck) Check() (warnings, errorList []error) {
  176. klog.V(1).Infof("validating availability of port %d", poc.port)
  177. errorList = []error{}
  178. ln, err := net.Listen("tcp", fmt.Sprintf(":%d", poc.port))
  179. if err != nil {
  180. errorList = append(errorList, errors.Errorf("Port %d is in use", poc.port))
  181. }
  182. if ln != nil {
  183. ln.Close()
  184. }
  185. return nil, errorList
  186. }
  187. // IsPrivilegedUserCheck verifies user is privileged (linux - root, windows - Administrator)
  188. type IsPrivilegedUserCheck struct{}
  189. // Name returns name for IsPrivilegedUserCheck
  190. func (IsPrivilegedUserCheck) Name() string {
  191. return "IsPrivilegedUser"
  192. }
  193. // IsDockerSystemdCheck verifies if Docker is setup to use systemd as the cgroup driver.
  194. type IsDockerSystemdCheck struct{}
  195. // Name returns name for IsDockerSystemdCheck
  196. func (IsDockerSystemdCheck) Name() string {
  197. return "IsDockerSystemdCheck"
  198. }
  199. // DirAvailableCheck checks if the given directory either does not exist, or is empty.
  200. type DirAvailableCheck struct {
  201. Path string
  202. Label string
  203. }
  204. // Name returns label for individual DirAvailableChecks. If not known, will return based on path.
  205. func (dac DirAvailableCheck) Name() string {
  206. if dac.Label != "" {
  207. return dac.Label
  208. }
  209. return fmt.Sprintf("DirAvailable-%s", strings.Replace(dac.Path, "/", "-", -1))
  210. }
  211. // Check validates if a directory does not exist or empty.
  212. func (dac DirAvailableCheck) Check() (warnings, errorList []error) {
  213. klog.V(1).Infof("validating the existence and emptiness of directory %s", dac.Path)
  214. errorList = []error{}
  215. // If it doesn't exist we are good:
  216. if _, err := os.Stat(dac.Path); os.IsNotExist(err) {
  217. return nil, nil
  218. }
  219. f, err := os.Open(dac.Path)
  220. if err != nil {
  221. errorList = append(errorList, errors.Wrapf(err, "unable to check if %s is empty", dac.Path))
  222. return nil, errorList
  223. }
  224. defer f.Close()
  225. _, err = f.Readdirnames(1)
  226. if err != io.EOF {
  227. errorList = append(errorList, errors.Errorf("%s is not empty", dac.Path))
  228. }
  229. return nil, errorList
  230. }
  231. // FileAvailableCheck checks that the given file does not already exist.
  232. type FileAvailableCheck struct {
  233. Path string
  234. Label string
  235. }
  236. // Name returns label for individual FileAvailableChecks. If not known, will return based on path.
  237. func (fac FileAvailableCheck) Name() string {
  238. if fac.Label != "" {
  239. return fac.Label
  240. }
  241. return fmt.Sprintf("FileAvailable-%s", strings.Replace(fac.Path, "/", "-", -1))
  242. }
  243. // Check validates if the given file does not already exist.
  244. func (fac FileAvailableCheck) Check() (warnings, errorList []error) {
  245. klog.V(1).Infof("validating the existence of file %s", fac.Path)
  246. errorList = []error{}
  247. if _, err := os.Stat(fac.Path); err == nil {
  248. errorList = append(errorList, errors.Errorf("%s already exists", fac.Path))
  249. }
  250. return nil, errorList
  251. }
  252. // FileExistingCheck checks that the given file does not already exist.
  253. type FileExistingCheck struct {
  254. Path string
  255. Label string
  256. }
  257. // Name returns label for individual FileExistingChecks. If not known, will return based on path.
  258. func (fac FileExistingCheck) Name() string {
  259. if fac.Label != "" {
  260. return fac.Label
  261. }
  262. return fmt.Sprintf("FileExisting-%s", strings.Replace(fac.Path, "/", "-", -1))
  263. }
  264. // Check validates if the given file already exists.
  265. func (fac FileExistingCheck) Check() (warnings, errorList []error) {
  266. klog.V(1).Infof("validating the existence of file %s", fac.Path)
  267. errorList = []error{}
  268. if _, err := os.Stat(fac.Path); err != nil {
  269. errorList = append(errorList, errors.Errorf("%s doesn't exist", fac.Path))
  270. }
  271. return nil, errorList
  272. }
  273. // FileContentCheck checks that the given file contains the string Content.
  274. type FileContentCheck struct {
  275. Path string
  276. Content []byte
  277. Label string
  278. }
  279. // Name returns label for individual FileContentChecks. If not known, will return based on path.
  280. func (fcc FileContentCheck) Name() string {
  281. if fcc.Label != "" {
  282. return fcc.Label
  283. }
  284. return fmt.Sprintf("FileContent-%s", strings.Replace(fcc.Path, "/", "-", -1))
  285. }
  286. // Check validates if the given file contains the given content.
  287. func (fcc FileContentCheck) Check() (warnings, errorList []error) {
  288. klog.V(1).Infof("validating the contents of file %s", fcc.Path)
  289. f, err := os.Open(fcc.Path)
  290. if err != nil {
  291. return nil, []error{errors.Errorf("%s does not exist", fcc.Path)}
  292. }
  293. lr := io.LimitReader(f, int64(len(fcc.Content)))
  294. defer f.Close()
  295. buf := &bytes.Buffer{}
  296. _, err = io.Copy(buf, lr)
  297. if err != nil {
  298. return nil, []error{errors.Errorf("%s could not be read", fcc.Path)}
  299. }
  300. if !bytes.Equal(buf.Bytes(), fcc.Content) {
  301. return nil, []error{errors.Errorf("%s contents are not set to %s", fcc.Path, fcc.Content)}
  302. }
  303. return nil, []error{}
  304. }
  305. // InPathCheck checks if the given executable is present in $PATH
  306. type InPathCheck struct {
  307. executable string
  308. mandatory bool
  309. exec utilsexec.Interface
  310. label string
  311. suggestion string
  312. }
  313. // Name returns label for individual InPathCheck. If not known, will return based on path.
  314. func (ipc InPathCheck) Name() string {
  315. if ipc.label != "" {
  316. return ipc.label
  317. }
  318. return fmt.Sprintf("FileExisting-%s", strings.Replace(ipc.executable, "/", "-", -1))
  319. }
  320. // Check validates if the given executable is present in the path.
  321. func (ipc InPathCheck) Check() (warnings, errs []error) {
  322. klog.V(1).Infof("validating the presence of executable %s", ipc.executable)
  323. _, err := ipc.exec.LookPath(ipc.executable)
  324. if err != nil {
  325. if ipc.mandatory {
  326. // Return as an error:
  327. return nil, []error{errors.Errorf("%s not found in system path", ipc.executable)}
  328. }
  329. // Return as a warning:
  330. warningMessage := fmt.Sprintf("%s not found in system path", ipc.executable)
  331. if ipc.suggestion != "" {
  332. warningMessage += fmt.Sprintf("\nSuggestion: %s", ipc.suggestion)
  333. }
  334. return []error{errors.New(warningMessage)}, nil
  335. }
  336. return nil, nil
  337. }
  338. // HostnameCheck checks if hostname match dns sub domain regex.
  339. // If hostname doesn't match this regex, kubelet will not launch static pods like kube-apiserver/kube-controller-manager and so on.
  340. type HostnameCheck struct {
  341. nodeName string
  342. }
  343. // Name will return Hostname as name for HostnameCheck
  344. func (HostnameCheck) Name() string {
  345. return "Hostname"
  346. }
  347. // Check validates if hostname match dns sub domain regex.
  348. func (hc HostnameCheck) Check() (warnings, errorList []error) {
  349. klog.V(1).Infoln("checking whether the given node name is reachable using net.LookupHost")
  350. errorList = []error{}
  351. warnings = []error{}
  352. addr, err := net.LookupHost(hc.nodeName)
  353. if addr == nil {
  354. warnings = append(warnings, errors.Errorf("hostname \"%s\" could not be reached", hc.nodeName))
  355. }
  356. if err != nil {
  357. warnings = append(warnings, errors.Wrapf(err, "hostname \"%s\"", hc.nodeName))
  358. }
  359. return warnings, errorList
  360. }
  361. // HTTPProxyCheck checks if https connection to specific host is going
  362. // to be done directly or over proxy. If proxy detected, it will return warning.
  363. type HTTPProxyCheck struct {
  364. Proto string
  365. Host string
  366. }
  367. // Name returns HTTPProxy as name for HTTPProxyCheck
  368. func (hst HTTPProxyCheck) Name() string {
  369. return "HTTPProxy"
  370. }
  371. // Check validates http connectivity type, direct or via proxy.
  372. func (hst HTTPProxyCheck) Check() (warnings, errorList []error) {
  373. klog.V(1).Infoln("validating if the connectivity type is via proxy or direct")
  374. u := (&url.URL{Scheme: hst.Proto, Host: hst.Host}).String()
  375. req, err := http.NewRequest("GET", u, nil)
  376. if err != nil {
  377. return nil, []error{err}
  378. }
  379. proxy, err := netutil.SetOldTransportDefaults(&http.Transport{}).Proxy(req)
  380. if err != nil {
  381. return nil, []error{err}
  382. }
  383. if proxy != nil {
  384. return []error{errors.Errorf("Connection to %q uses proxy %q. If that is not intended, adjust your proxy settings", u, proxy)}, nil
  385. }
  386. return nil, nil
  387. }
  388. // HTTPProxyCIDRCheck checks if https connection to specific subnet is going
  389. // to be done directly or over proxy. If proxy detected, it will return warning.
  390. // Similar to HTTPProxyCheck above, but operates with subnets and uses API
  391. // machinery transport defaults to simulate kube-apiserver accessing cluster
  392. // services and pods.
  393. type HTTPProxyCIDRCheck struct {
  394. Proto string
  395. CIDR string
  396. }
  397. // Name will return HTTPProxyCIDR as name for HTTPProxyCIDRCheck
  398. func (HTTPProxyCIDRCheck) Name() string {
  399. return "HTTPProxyCIDR"
  400. }
  401. // Check validates http connectivity to first IP address in the CIDR.
  402. // If it is not directly connected and goes via proxy it will produce warning.
  403. func (subnet HTTPProxyCIDRCheck) Check() (warnings, errorList []error) {
  404. klog.V(1).Infoln("validating http connectivity to first IP address in the CIDR")
  405. if len(subnet.CIDR) == 0 {
  406. return nil, nil
  407. }
  408. _, cidr, err := net.ParseCIDR(subnet.CIDR)
  409. if err != nil {
  410. return nil, []error{errors.Wrapf(err, "error parsing CIDR %q", subnet.CIDR)}
  411. }
  412. testIP, err := ipallocator.GetIndexedIP(cidr, 1)
  413. if err != nil {
  414. return nil, []error{errors.Wrapf(err, "unable to get first IP address from the given CIDR (%s)", cidr.String())}
  415. }
  416. testIPstring := testIP.String()
  417. if len(testIP) == net.IPv6len {
  418. testIPstring = fmt.Sprintf("[%s]:1234", testIP)
  419. }
  420. url := fmt.Sprintf("%s://%s/", subnet.Proto, testIPstring)
  421. req, err := http.NewRequest("GET", url, nil)
  422. if err != nil {
  423. return nil, []error{err}
  424. }
  425. // Utilize same transport defaults as it will be used by API server
  426. proxy, err := netutil.SetOldTransportDefaults(&http.Transport{}).Proxy(req)
  427. if err != nil {
  428. return nil, []error{err}
  429. }
  430. if proxy != nil {
  431. return []error{errors.Errorf("connection to %q uses proxy %q. This may lead to malfunctional cluster setup. Make sure that Pod and Services IP ranges specified correctly as exceptions in proxy configuration", subnet.CIDR, proxy)}, nil
  432. }
  433. return nil, nil
  434. }
  435. // SystemVerificationCheck defines struct used for running the system verification node check in test/e2e_node/system
  436. type SystemVerificationCheck struct {
  437. IsDocker bool
  438. }
  439. // Name will return SystemVerification as name for SystemVerificationCheck
  440. func (SystemVerificationCheck) Name() string {
  441. return "SystemVerification"
  442. }
  443. // Check runs all individual checks
  444. func (sysver SystemVerificationCheck) Check() (warnings, errorList []error) {
  445. klog.V(1).Infoln("running all checks")
  446. // Create a buffered writer and choose a quite large value (1M) and suppose the output from the system verification test won't exceed the limit
  447. // Run the system verification check, but write to out buffered writer instead of stdout
  448. bufw := bufio.NewWriterSize(os.Stdout, 1*1024*1024)
  449. reporter := &system.StreamReporter{WriteStream: bufw}
  450. var errs []error
  451. var warns []error
  452. // All the common validators we'd like to run:
  453. var validators = []system.Validator{
  454. &system.KernelValidator{Reporter: reporter}}
  455. // run the docker validator only with docker runtime
  456. if sysver.IsDocker {
  457. validators = append(validators, &system.DockerValidator{Reporter: reporter})
  458. }
  459. if runtime.GOOS == "linux" {
  460. //add linux validators
  461. validators = append(validators,
  462. &system.OSValidator{Reporter: reporter},
  463. &system.CgroupsValidator{Reporter: reporter})
  464. }
  465. // Run all validators
  466. for _, v := range validators {
  467. warn, err := v.Validate(system.DefaultSysSpec)
  468. if err != nil {
  469. errs = append(errs, err)
  470. }
  471. if warn != nil {
  472. warns = append(warns, warn)
  473. }
  474. }
  475. if len(errs) != 0 {
  476. // Only print the output from the system verification check if the check failed
  477. fmt.Println("[preflight] The system verification failed. Printing the output from the verification:")
  478. bufw.Flush()
  479. return warns, errs
  480. }
  481. return warns, nil
  482. }
  483. // KubernetesVersionCheck validates Kubernetes and kubeadm versions
  484. type KubernetesVersionCheck struct {
  485. KubeadmVersion string
  486. KubernetesVersion string
  487. }
  488. // Name will return KubernetesVersion as name for KubernetesVersionCheck
  489. func (KubernetesVersionCheck) Name() string {
  490. return "KubernetesVersion"
  491. }
  492. // Check validates Kubernetes and kubeadm versions
  493. func (kubever KubernetesVersionCheck) Check() (warnings, errorList []error) {
  494. klog.V(1).Infoln("validating Kubernetes and kubeadm version")
  495. // Skip this check for "super-custom builds", where apimachinery/the overall codebase version is not set.
  496. if strings.HasPrefix(kubever.KubeadmVersion, "v0.0.0") {
  497. return nil, nil
  498. }
  499. kadmVersion, err := versionutil.ParseSemantic(kubever.KubeadmVersion)
  500. if err != nil {
  501. return nil, []error{errors.Wrapf(err, "couldn't parse kubeadm version %q", kubever.KubeadmVersion)}
  502. }
  503. k8sVersion, err := versionutil.ParseSemantic(kubever.KubernetesVersion)
  504. if err != nil {
  505. return nil, []error{errors.Wrapf(err, "couldn't parse Kubernetes version %q", kubever.KubernetesVersion)}
  506. }
  507. // Checks if k8sVersion greater or equal than the first unsupported versions by current version of kubeadm,
  508. // that is major.minor+1 (all patch and pre-releases versions included)
  509. // NB. in semver patches number is a numeric, while prerelease is a string where numeric identifiers always have lower precedence than non-numeric identifiers.
  510. // thus setting the value to x.y.0-0 we are defining the very first patch - prereleases within x.y minor release.
  511. firstUnsupportedVersion := versionutil.MustParseSemantic(fmt.Sprintf("%d.%d.%s", kadmVersion.Major(), kadmVersion.Minor()+1, "0-0"))
  512. if k8sVersion.AtLeast(firstUnsupportedVersion) {
  513. return []error{errors.Errorf("Kubernetes version is greater than kubeadm version. Please consider to upgrade kubeadm. Kubernetes version: %s. Kubeadm version: %d.%d.x", k8sVersion, kadmVersion.Components()[0], kadmVersion.Components()[1])}, nil
  514. }
  515. return nil, nil
  516. }
  517. // KubeletVersionCheck validates installed kubelet version
  518. type KubeletVersionCheck struct {
  519. KubernetesVersion string
  520. exec utilsexec.Interface
  521. }
  522. // Name will return KubeletVersion as name for KubeletVersionCheck
  523. func (KubeletVersionCheck) Name() string {
  524. return "KubeletVersion"
  525. }
  526. // Check validates kubelet version. It should be not less than minimal supported version
  527. func (kubever KubeletVersionCheck) Check() (warnings, errorList []error) {
  528. klog.V(1).Infoln("validating kubelet version")
  529. kubeletVersion, err := GetKubeletVersion(kubever.exec)
  530. if err != nil {
  531. return nil, []error{errors.Wrap(err, "couldn't get kubelet version")}
  532. }
  533. if kubeletVersion.LessThan(kubeadmconstants.MinimumKubeletVersion) {
  534. return nil, []error{errors.Errorf("Kubelet version %q is lower than kubeadm can support. Please upgrade kubelet", kubeletVersion)}
  535. }
  536. if kubever.KubernetesVersion != "" {
  537. k8sVersion, err := versionutil.ParseSemantic(kubever.KubernetesVersion)
  538. if err != nil {
  539. return nil, []error{errors.Wrapf(err, "couldn't parse Kubernetes version %q", kubever.KubernetesVersion)}
  540. }
  541. if kubeletVersion.Major() > k8sVersion.Major() || kubeletVersion.Minor() > k8sVersion.Minor() {
  542. return nil, []error{errors.Errorf("the kubelet version is higher than the control plane version. This is not a supported version skew and may lead to a malfunctional cluster. Kubelet version: %q Control plane version: %q", kubeletVersion, k8sVersion)}
  543. }
  544. }
  545. return nil, nil
  546. }
  547. // SwapCheck warns if swap is enabled
  548. type SwapCheck struct{}
  549. // Name will return Swap as name for SwapCheck
  550. func (SwapCheck) Name() string {
  551. return "Swap"
  552. }
  553. // Check validates whether swap is enabled or not
  554. func (swc SwapCheck) Check() (warnings, errorList []error) {
  555. klog.V(1).Infoln("validating whether swap is enabled or not")
  556. f, err := os.Open("/proc/swaps")
  557. if err != nil {
  558. // /proc/swaps not available, thus no reasons to warn
  559. return nil, nil
  560. }
  561. defer f.Close()
  562. var buf []string
  563. scanner := bufio.NewScanner(f)
  564. for scanner.Scan() {
  565. buf = append(buf, scanner.Text())
  566. }
  567. if err := scanner.Err(); err != nil {
  568. return nil, []error{errors.Wrap(err, "error parsing /proc/swaps")}
  569. }
  570. if len(buf) > 1 {
  571. return nil, []error{errors.New("running with swap on is not supported. Please disable swap")}
  572. }
  573. return nil, nil
  574. }
  575. type etcdVersionResponse struct {
  576. Etcdserver string `json:"etcdserver"`
  577. Etcdcluster string `json:"etcdcluster"`
  578. }
  579. // ExternalEtcdVersionCheck checks if version of external etcd meets the demand of kubeadm
  580. type ExternalEtcdVersionCheck struct {
  581. Etcd kubeadmapi.Etcd
  582. }
  583. // Name will return ExternalEtcdVersion as name for ExternalEtcdVersionCheck
  584. func (ExternalEtcdVersionCheck) Name() string {
  585. return "ExternalEtcdVersion"
  586. }
  587. // Check validates external etcd version
  588. // TODO: Use the official etcd Golang client for this instead?
  589. func (evc ExternalEtcdVersionCheck) Check() (warnings, errorList []error) {
  590. klog.V(1).Infoln("validating the external etcd version")
  591. // Return quickly if the user isn't using external etcd
  592. if evc.Etcd.External.Endpoints == nil {
  593. return nil, nil
  594. }
  595. var config *tls.Config
  596. var err error
  597. if config, err = evc.configRootCAs(config); err != nil {
  598. errorList = append(errorList, err)
  599. return nil, errorList
  600. }
  601. if config, err = evc.configCertAndKey(config); err != nil {
  602. errorList = append(errorList, err)
  603. return nil, errorList
  604. }
  605. client := evc.getHTTPClient(config)
  606. for _, endpoint := range evc.Etcd.External.Endpoints {
  607. if _, err := url.Parse(endpoint); err != nil {
  608. errorList = append(errorList, errors.Wrapf(err, "failed to parse external etcd endpoint %s", endpoint))
  609. continue
  610. }
  611. resp := etcdVersionResponse{}
  612. var err error
  613. versionURL := fmt.Sprintf("%s/%s", endpoint, "version")
  614. if tmpVersionURL, err := purell.NormalizeURLString(versionURL, purell.FlagRemoveDuplicateSlashes); err != nil {
  615. errorList = append(errorList, errors.Wrapf(err, "failed to normalize external etcd version url %s", versionURL))
  616. continue
  617. } else {
  618. versionURL = tmpVersionURL
  619. }
  620. if err = getEtcdVersionResponse(client, versionURL, &resp); err != nil {
  621. errorList = append(errorList, err)
  622. continue
  623. }
  624. etcdVersion, err := semver.Parse(resp.Etcdserver)
  625. if err != nil {
  626. errorList = append(errorList, errors.Wrapf(err, "couldn't parse external etcd version %q", resp.Etcdserver))
  627. continue
  628. }
  629. if etcdVersion.LT(minExternalEtcdVersion) {
  630. errorList = append(errorList, errors.Errorf("this version of kubeadm only supports external etcd version >= %s. Current version: %s", kubeadmconstants.MinExternalEtcdVersion, resp.Etcdserver))
  631. continue
  632. }
  633. }
  634. return nil, errorList
  635. }
  636. // configRootCAs configures and returns a reference to tls.Config instance if CAFile is provided
  637. func (evc ExternalEtcdVersionCheck) configRootCAs(config *tls.Config) (*tls.Config, error) {
  638. var CACertPool *x509.CertPool
  639. if evc.Etcd.External.CAFile != "" {
  640. CACert, err := ioutil.ReadFile(evc.Etcd.External.CAFile)
  641. if err != nil {
  642. return nil, errors.Wrapf(err, "couldn't load external etcd's server certificate %s", evc.Etcd.External.CAFile)
  643. }
  644. CACertPool = x509.NewCertPool()
  645. CACertPool.AppendCertsFromPEM(CACert)
  646. }
  647. if CACertPool != nil {
  648. if config == nil {
  649. config = &tls.Config{}
  650. }
  651. config.RootCAs = CACertPool
  652. }
  653. return config, nil
  654. }
  655. // configCertAndKey configures and returns a reference to tls.Config instance if CertFile and KeyFile pair is provided
  656. func (evc ExternalEtcdVersionCheck) configCertAndKey(config *tls.Config) (*tls.Config, error) {
  657. var cert tls.Certificate
  658. if evc.Etcd.External.CertFile != "" && evc.Etcd.External.KeyFile != "" {
  659. var err error
  660. cert, err = tls.LoadX509KeyPair(evc.Etcd.External.CertFile, evc.Etcd.External.KeyFile)
  661. if err != nil {
  662. return nil, errors.Wrapf(err, "couldn't load external etcd's certificate and key pair %s, %s", evc.Etcd.External.CertFile, evc.Etcd.External.KeyFile)
  663. }
  664. if config == nil {
  665. config = &tls.Config{}
  666. }
  667. config.Certificates = []tls.Certificate{cert}
  668. }
  669. return config, nil
  670. }
  671. func (evc ExternalEtcdVersionCheck) getHTTPClient(config *tls.Config) *http.Client {
  672. if config != nil {
  673. transport := netutil.SetOldTransportDefaults(&http.Transport{
  674. TLSClientConfig: config,
  675. })
  676. return &http.Client{
  677. Transport: transport,
  678. Timeout: externalEtcdRequestTimeout,
  679. }
  680. }
  681. return &http.Client{Timeout: externalEtcdRequestTimeout, Transport: netutil.SetOldTransportDefaults(&http.Transport{})}
  682. }
  683. func getEtcdVersionResponse(client *http.Client, url string, target interface{}) error {
  684. loopCount := externalEtcdRequestRetries + 1
  685. var err error
  686. var stopRetry bool
  687. for loopCount > 0 {
  688. if loopCount <= externalEtcdRequestRetries {
  689. time.Sleep(externalEtcdRequestInterval)
  690. }
  691. stopRetry, err = func() (stopRetry bool, err error) {
  692. r, err := client.Get(url)
  693. if err != nil {
  694. loopCount--
  695. return false, err
  696. }
  697. defer r.Body.Close()
  698. if r != nil && r.StatusCode >= 500 && r.StatusCode <= 599 {
  699. loopCount--
  700. return false, errors.Errorf("server responded with non-successful status: %s", r.Status)
  701. }
  702. return true, json.NewDecoder(r.Body).Decode(target)
  703. }()
  704. if stopRetry {
  705. break
  706. }
  707. }
  708. return err
  709. }
  710. // ImagePullCheck will pull container images used by kubeadm
  711. type ImagePullCheck struct {
  712. runtime utilruntime.ContainerRuntime
  713. imageList []string
  714. }
  715. // Name returns the label for ImagePullCheck
  716. func (ImagePullCheck) Name() string {
  717. return "ImagePull"
  718. }
  719. // Check pulls images required by kubeadm. This is a mutating check
  720. func (ipc ImagePullCheck) Check() (warnings, errorList []error) {
  721. for _, image := range ipc.imageList {
  722. ret, err := ipc.runtime.ImageExists(image)
  723. if ret && err == nil {
  724. klog.V(1).Infof("image exists: %s", image)
  725. continue
  726. }
  727. if err != nil {
  728. errorList = append(errorList, errors.Wrapf(err, "failed to check if image %s exists", image))
  729. }
  730. klog.V(1).Infof("pulling %s", image)
  731. if err := ipc.runtime.PullImage(image); err != nil {
  732. errorList = append(errorList, errors.Wrapf(err, "failed to pull image %s", image))
  733. }
  734. }
  735. return warnings, errorList
  736. }
  737. // NumCPUCheck checks if current number of CPUs is not less than required
  738. type NumCPUCheck struct {
  739. NumCPU int
  740. }
  741. // Name returns the label for NumCPUCheck
  742. func (NumCPUCheck) Name() string {
  743. return "NumCPU"
  744. }
  745. // Check number of CPUs required by kubeadm
  746. func (ncc NumCPUCheck) Check() (warnings, errorList []error) {
  747. numCPU := runtime.NumCPU()
  748. if numCPU < ncc.NumCPU {
  749. errorList = append(errorList, errors.Errorf("the number of available CPUs %d is less than the required %d", numCPU, ncc.NumCPU))
  750. }
  751. return warnings, errorList
  752. }
  753. // IPVSProxierCheck tests if IPVS proxier can be used.
  754. type IPVSProxierCheck struct {
  755. exec utilsexec.Interface
  756. }
  757. // Name returns label for IPVSProxierCheck
  758. func (r IPVSProxierCheck) Name() string {
  759. return "IPVSProxierCheck"
  760. }
  761. // RunInitNodeChecks executes all individual, applicable to control-plane node checks.
  762. // The boolean flag 'isSecondaryControlPlane' controls whether we are running checks in a --join-control-plane scenario.
  763. // The boolean flag 'downloadCerts' controls whether we should skip checks on certificates because we are downloading them.
  764. // If the flag is set to true we should skip checks already executed by RunJoinNodeChecks and RunOptionalJoinNodeChecks.
  765. func RunInitNodeChecks(execer utilsexec.Interface, cfg *kubeadmapi.InitConfiguration, ignorePreflightErrors sets.String, isSecondaryControlPlane bool, downloadCerts bool) error {
  766. if !isSecondaryControlPlane {
  767. // First, check if we're root separately from the other preflight checks and fail fast
  768. if err := RunRootCheckOnly(ignorePreflightErrors); err != nil {
  769. return err
  770. }
  771. }
  772. manifestsDir := filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.ManifestsSubDirName)
  773. checks := []Checker{
  774. NumCPUCheck{NumCPU: kubeadmconstants.ControlPlaneNumCPU},
  775. KubernetesVersionCheck{KubernetesVersion: cfg.KubernetesVersion, KubeadmVersion: kubeadmversion.Get().GitVersion},
  776. FirewalldCheck{ports: []int{int(cfg.LocalAPIEndpoint.BindPort), ports.KubeletPort}},
  777. PortOpenCheck{port: int(cfg.LocalAPIEndpoint.BindPort)},
  778. PortOpenCheck{port: ports.InsecureSchedulerPort},
  779. PortOpenCheck{port: ports.InsecureKubeControllerManagerPort},
  780. FileAvailableCheck{Path: kubeadmconstants.GetStaticPodFilepath(kubeadmconstants.KubeAPIServer, manifestsDir)},
  781. FileAvailableCheck{Path: kubeadmconstants.GetStaticPodFilepath(kubeadmconstants.KubeControllerManager, manifestsDir)},
  782. FileAvailableCheck{Path: kubeadmconstants.GetStaticPodFilepath(kubeadmconstants.KubeScheduler, manifestsDir)},
  783. FileAvailableCheck{Path: kubeadmconstants.GetStaticPodFilepath(kubeadmconstants.Etcd, manifestsDir)},
  784. HTTPProxyCheck{Proto: "https", Host: cfg.LocalAPIEndpoint.AdvertiseAddress},
  785. HTTPProxyCIDRCheck{Proto: "https", CIDR: cfg.Networking.ServiceSubnet},
  786. HTTPProxyCIDRCheck{Proto: "https", CIDR: cfg.Networking.PodSubnet},
  787. }
  788. if !isSecondaryControlPlane {
  789. checks = addCommonChecks(execer, cfg.KubernetesVersion, &cfg.NodeRegistration, checks)
  790. // Check if IVPS kube-proxy mode is supported
  791. if cfg.ComponentConfigs.KubeProxy != nil && cfg.ComponentConfigs.KubeProxy.Mode == ipvsutil.IPVSProxyMode {
  792. checks = append(checks, IPVSProxierCheck{exec: execer})
  793. }
  794. // Check if Bridge-netfilter and IPv6 relevant flags are set
  795. if ip := net.ParseIP(cfg.LocalAPIEndpoint.AdvertiseAddress); ip != nil {
  796. if ip.To4() == nil && ip.To16() != nil {
  797. checks = append(checks,
  798. FileContentCheck{Path: bridgenf6, Content: []byte{'1'}},
  799. FileContentCheck{Path: ipv6DefaultForwarding, Content: []byte{'1'}},
  800. )
  801. }
  802. }
  803. // if using an external etcd
  804. if cfg.Etcd.External != nil {
  805. // Check external etcd version before creating the cluster
  806. checks = append(checks, ExternalEtcdVersionCheck{Etcd: cfg.Etcd})
  807. }
  808. }
  809. if cfg.Etcd.Local != nil {
  810. // Only do etcd related checks when required to install a local etcd
  811. checks = append(checks,
  812. PortOpenCheck{port: kubeadmconstants.EtcdListenClientPort},
  813. PortOpenCheck{port: kubeadmconstants.EtcdListenPeerPort},
  814. DirAvailableCheck{Path: cfg.Etcd.Local.DataDir},
  815. )
  816. }
  817. if cfg.Etcd.External != nil && !(isSecondaryControlPlane && downloadCerts) {
  818. // Only check etcd certificates when using an external etcd and not joining with automatic download of certs
  819. if cfg.Etcd.External.CAFile != "" {
  820. checks = append(checks, FileExistingCheck{Path: cfg.Etcd.External.CAFile, Label: "ExternalEtcdClientCertificates"})
  821. }
  822. if cfg.Etcd.External.CertFile != "" {
  823. checks = append(checks, FileExistingCheck{Path: cfg.Etcd.External.CertFile, Label: "ExternalEtcdClientCertificates"})
  824. }
  825. if cfg.Etcd.External.KeyFile != "" {
  826. checks = append(checks, FileExistingCheck{Path: cfg.Etcd.External.KeyFile, Label: "ExternalEtcdClientCertificates"})
  827. }
  828. }
  829. return RunChecks(checks, os.Stderr, ignorePreflightErrors)
  830. }
  831. // RunJoinNodeChecks executes all individual, applicable to node checks.
  832. func RunJoinNodeChecks(execer utilsexec.Interface, cfg *kubeadmapi.JoinConfiguration, ignorePreflightErrors sets.String) error {
  833. // First, check if we're root separately from the other preflight checks and fail fast
  834. if err := RunRootCheckOnly(ignorePreflightErrors); err != nil {
  835. return err
  836. }
  837. checks := []Checker{
  838. DirAvailableCheck{Path: filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.ManifestsSubDirName)},
  839. FileAvailableCheck{Path: filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.KubeletKubeConfigFileName)},
  840. FileAvailableCheck{Path: filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.KubeletBootstrapKubeConfigFileName)},
  841. }
  842. checks = addCommonChecks(execer, "", &cfg.NodeRegistration, checks)
  843. if cfg.ControlPlane == nil {
  844. checks = append(checks, FileAvailableCheck{Path: cfg.CACertPath})
  845. }
  846. addIPv6Checks := false
  847. if cfg.Discovery.BootstrapToken != nil {
  848. ipstr, _, err := net.SplitHostPort(cfg.Discovery.BootstrapToken.APIServerEndpoint)
  849. if err == nil {
  850. checks = append(checks,
  851. HTTPProxyCheck{Proto: "https", Host: ipstr},
  852. )
  853. if !addIPv6Checks {
  854. if ip := net.ParseIP(ipstr); ip != nil {
  855. if ip.To4() == nil && ip.To16() != nil {
  856. addIPv6Checks = true
  857. }
  858. }
  859. }
  860. }
  861. }
  862. if addIPv6Checks {
  863. checks = append(checks,
  864. FileContentCheck{Path: bridgenf6, Content: []byte{'1'}},
  865. FileContentCheck{Path: ipv6DefaultForwarding, Content: []byte{'1'}},
  866. )
  867. }
  868. return RunChecks(checks, os.Stderr, ignorePreflightErrors)
  869. }
  870. // RunOptionalJoinNodeChecks executes all individual, applicable to node configuration dependant checks
  871. func RunOptionalJoinNodeChecks(execer utilsexec.Interface, cfg *kubeadmapi.ClusterConfiguration, ignorePreflightErrors sets.String) error {
  872. checks := []Checker{}
  873. // Check if IPVS kube-proxy mode is supported
  874. if cfg.ComponentConfigs.KubeProxy != nil && cfg.ComponentConfigs.KubeProxy.Mode == ipvsutil.IPVSProxyMode {
  875. checks = append(checks, IPVSProxierCheck{exec: execer})
  876. }
  877. return RunChecks(checks, os.Stderr, ignorePreflightErrors)
  878. }
  879. // addCommonChecks is a helper function to deplicate checks that are common between both the
  880. // kubeadm init and join commands
  881. func addCommonChecks(execer utilsexec.Interface, k8sVersion string, nodeReg *kubeadmapi.NodeRegistrationOptions, checks []Checker) []Checker {
  882. containerRuntime, err := utilruntime.NewContainerRuntime(execer, nodeReg.CRISocket)
  883. isDocker := false
  884. if err != nil {
  885. fmt.Printf("[preflight] WARNING: Couldn't create the interface used for talking to the container runtime: %v\n", err)
  886. } else {
  887. checks = append(checks, ContainerRuntimeCheck{runtime: containerRuntime})
  888. if containerRuntime.IsDocker() {
  889. isDocker = true
  890. checks = append(checks, ServiceCheck{Service: "docker", CheckIfActive: true})
  891. // Linux only
  892. // TODO: support other CRIs for this check eventually
  893. // https://github.com/kubernetes/kubeadm/issues/874
  894. checks = append(checks, IsDockerSystemdCheck{})
  895. }
  896. }
  897. // non-windows checks
  898. if runtime.GOOS == "linux" {
  899. if !isDocker {
  900. checks = append(checks, InPathCheck{executable: "crictl", mandatory: true, exec: execer})
  901. }
  902. checks = append(checks,
  903. FileContentCheck{Path: bridgenf, Content: []byte{'1'}},
  904. FileContentCheck{Path: ipv4Forward, Content: []byte{'1'}},
  905. SwapCheck{},
  906. InPathCheck{executable: "ip", mandatory: true, exec: execer},
  907. InPathCheck{executable: "iptables", mandatory: true, exec: execer},
  908. InPathCheck{executable: "mount", mandatory: true, exec: execer},
  909. InPathCheck{executable: "nsenter", mandatory: true, exec: execer},
  910. InPathCheck{executable: "ebtables", mandatory: false, exec: execer},
  911. InPathCheck{executable: "ethtool", mandatory: false, exec: execer},
  912. InPathCheck{executable: "socat", mandatory: false, exec: execer},
  913. InPathCheck{executable: "tc", mandatory: false, exec: execer},
  914. InPathCheck{executable: "touch", mandatory: false, exec: execer})
  915. }
  916. checks = append(checks,
  917. SystemVerificationCheck{IsDocker: isDocker},
  918. HostnameCheck{nodeName: nodeReg.Name},
  919. KubeletVersionCheck{KubernetesVersion: k8sVersion, exec: execer},
  920. ServiceCheck{Service: "kubelet", CheckIfActive: false},
  921. PortOpenCheck{port: ports.KubeletPort})
  922. return checks
  923. }
  924. // RunRootCheckOnly initializes checks slice of structs and call RunChecks
  925. func RunRootCheckOnly(ignorePreflightErrors sets.String) error {
  926. checks := []Checker{
  927. IsPrivilegedUserCheck{},
  928. }
  929. return RunChecks(checks, os.Stderr, ignorePreflightErrors)
  930. }
  931. // RunPullImagesCheck will pull images kubeadm needs if they are not found on the system
  932. func RunPullImagesCheck(execer utilsexec.Interface, cfg *kubeadmapi.InitConfiguration, ignorePreflightErrors sets.String) error {
  933. containerRuntime, err := utilruntime.NewContainerRuntime(utilsexec.New(), cfg.NodeRegistration.CRISocket)
  934. if err != nil {
  935. return err
  936. }
  937. checks := []Checker{
  938. ImagePullCheck{runtime: containerRuntime, imageList: images.GetControlPlaneImages(&cfg.ClusterConfiguration)},
  939. }
  940. return RunChecks(checks, os.Stderr, ignorePreflightErrors)
  941. }
  942. // RunChecks runs each check, displays it's warnings/errors, and once all
  943. // are processed will exit if any errors occurred.
  944. func RunChecks(checks []Checker, ww io.Writer, ignorePreflightErrors sets.String) error {
  945. var errsBuffer bytes.Buffer
  946. for _, c := range checks {
  947. name := c.Name()
  948. warnings, errs := c.Check()
  949. if setHasItemOrAll(ignorePreflightErrors, name) {
  950. // Decrease severity of errors to warnings for this check
  951. warnings = append(warnings, errs...)
  952. errs = []error{}
  953. }
  954. for _, w := range warnings {
  955. io.WriteString(ww, fmt.Sprintf("\t[WARNING %s]: %v\n", name, w))
  956. }
  957. for _, i := range errs {
  958. errsBuffer.WriteString(fmt.Sprintf("\t[ERROR %s]: %v\n", name, i.Error()))
  959. }
  960. }
  961. if errsBuffer.Len() > 0 {
  962. return &Error{Msg: errsBuffer.String()}
  963. }
  964. return nil
  965. }
  966. // setHasItemOrAll is helper function that return true if item is present in the set (case insensitive) or special key 'all' is present
  967. func setHasItemOrAll(s sets.String, item string) bool {
  968. if s.Has("all") || s.Has(strings.ToLower(item)) {
  969. return true
  970. }
  971. return false
  972. }