Remove the kstatus module

This commit is contained in:
Morten Torkildsen
2020-06-30 11:34:23 -07:00
parent 7ad2791072
commit 0e44202c20
44 changed files with 8 additions and 6463 deletions

View File

@@ -340,6 +340,7 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/qri-io/starlib v0.4.2-0.20200213133954-ff2e8cd5ef8d h1:K6eOUihrFLdZjZnA4XlRp864fmWXv9YTIk7VPLhRacA=
github.com/qri-io/starlib v0.4.2-0.20200213133954-ff2e8cd5ef8d/go.mod h1:7DPO4domFU579Ga6E61sB9VFNaniPVwJP5C4bBCu3wA=
github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
@@ -423,6 +424,7 @@ go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qL
go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.starlark.net v0.0.0-20190528202925-30ae18b8564f/go.mod h1:c1/X6cHgvdXj6pUlmWKMkuqRnW4K8x2vwt6JAaaircg=
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 h1:+FNtrFTmVw0YZGpBGX56XDee331t6JAXeK2bcyhLOOc=
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=

View File

@@ -1,54 +0,0 @@
# Copyright 2019 The Kubernetes Authors.
# SPDX-License-Identifier: Apache-2.0
run:
deadline: 5m
linters:
# please, do not use `enable-all`: it's deprecated and will be removed soon.
# inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint
disable-all: true
enable:
- bodyclose
- deadcode
- depguard
- dogsled
- dupl
- errcheck
# - funlen
- gochecknoinits
- goconst
- gocritic
- gocyclo
- gofmt
- goimports
- golint
- gosec
- gosimple
- govet
- ineffassign
- interfacer
- lll
- misspell
- nakedret
- scopelint
- staticcheck
- structcheck
- stylecheck
- typecheck
- unconvert
- unparam
- unused
- varcheck
- whitespace
linters-settings:
dupl:
threshold: 400
lll:
line-length: 170
gocyclo:
min-complexity: 30
golint:
min-confidence: 0.85

View File

@@ -1,2 +0,0 @@
Copyright {{.Year}} {{.Holder}}
SPDX-License-Identifier: Apache-2.0

View File

@@ -1,34 +0,0 @@
# Copyright 2019 The Kubernetes Authors.
# SPDX-License-Identifier: Apache-2.0
.PHONY: generate license fix vet fmt test lint tidy
GOPATH := $(shell go env GOPATH)
all: generate license fix vet fmt test lint tidy
fix:
go fix ./...
fmt:
go fmt ./...
generate:
go generate ./...
license:
(which $(GOPATH)/bin/addlicense || go get github.com/google/addlicense)
$(GOPATH)/bin/addlicense -y 2019 -c "The Kubernetes Authors." -f LICENSE_TEMPLATE .
tidy:
go mod tidy
lint:
(which $(GOPATH)/bin/golangci-lint || go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.19.1)
$(GOPATH)/bin/golangci-lint run ./...
test:
go test -cover ./...
vet:
go vet ./...

View File

@@ -1,115 +0,0 @@
# kstatus
kstatus provides tools for checking the status of Kubernetes resources. The primary use case is knowing when
(or if) a given set of resources in cluster has successfully reconciled an apply operation.
## Concepts
This effort has several goals, some with a shorter timeline than others. Initially, we want to provide
a library that makes it easier to decide when changes to a set of resources have been reconciled in a cluster.
To support types that do not yet publish status information, we will initially fallback on type specific rules.
The library already contains rules for many of the most common built-in types such a Deployment and StatefulSet.
For custom resource definitions (CRDs), there currently isn't much guidance on which properties should be exposed in the status
object and which conditions should be used. As part of this effort we want to define a set of standard conditions
that the library will understand and that we encourage developers to adopt in their CRDs. These standard conditions will
be focused on providing the necessary information for understanding status of the reconcile after `apply` and it is not
expected that these will necessarily be the only conditions exposed in a custom resource. Developers will be free to add as many conditions
as they wish, but if the CRDs adopt the standard conditions defined here, this library will handle them correctly.
The `status` objects for built-in types don't all conform to a common behavior. Not all built-in types expose conditions,
and even among the types that does, the types of conditions vary widely. Long-term, we hope to add support for the
standard conditions to the built-in types as well. This would remove the need for type-specific rules for determining
status.
### Statuses
The library currently defines the following statuses for resource:
* __InProgress__: The actual state of the resource has not yet reached the desired state as specified in the
resource manifest, i.e. the resource reconcile has not yet completed. Newly created resources will usually
start with this status, although some resources like ConfigMaps are Current immediately.
* __Failed__: The process of reconciling the actual state with the desired state has encountered and error
or it has made insufficient progress.
* __Current__: The actual state of the resource matches the desired state. The reconcile process is considered
complete until there are changes to either the desired or the actual state.
* __Terminating__: The resource is in the process of being deleted.
* __Unknown__: This is for situations when the library are unable to determine the status of a resource.
### Conditions
The conditions defined in the library are designed to adhere to the "abnormal-true" pattern, i.e. that
conditions are present and with a value of true whenever something unusual happens. So the absence of
any conditions means everything is normal. Normal in this situation simply means that the latest observed
generation of the resource manifest by the controller have been fully reconciled with the actual state.
* __InProgress__: The controller is currently working on reconciling the latest changes.
* __Failed__: The controller has encountered an error during the reconcile process or it has made
insufficient progress (timeout).
The use of the "abnormal-true" pattern has some challenges. If the controller is not running, or for some
reason not able to update the resource, it will look like it is in a good state when that is not true. The
solution to this issue is to adopt the pattern used by several of the built-in types where there is an
`observedGeneration` property on the status object which is set by the controller during the reconcile loop.
If the `generation` and the `observedGeneration` of a resource does not match, it means there are changes
that the controller has not yet seen, and therefore not acted upon.
## Features
The library is currently separated into two packages, one that provides the basic functionality, and another that
builds upon the basics to provide a higher level API.
**sigs.k8s.io/kustomize/kstatus/status**: Provides two basic functions. First, it provides the `Compute` function
that takes a single resource and computes the status for this resource based on the fields in the status object for
the resource. Second, it provides the `Augment` function that computes the appropriate standard conditions based on
the status object and then amends them to the conditions in the resource. Both of these functions currently operate
on Unstructured types, but this should eventually be changed to rely on the kyaml library. Both of these functions
compute the status and conditions solely based on the data in the resource passed in. It does not communicate with
a cluster to get the latest state of the resources.
**sigs.k8s.io/kustomize/kstatus/wait**: This package builds upon the status package and provides functionality that
will fetch the latest state from a cluster. It provides the `FetchAndResolve` function that takes list of resource
identifiers, fetches the latest state for all the resources from the cluster, computes the status for all of them and
returns the results. `WaitForStatus` accepts a list of resource identifiers and will poll cluster for the status of
the resources until all resources have reached the `Current` status.
## Challenges
### Status is not obvious for all resource types
For some types of resources, it is pretty clear what the different statuses mean. For others, it
is far less obvious. For example, what does it mean that a PodDisruptionBudget is Current? Based on
the assumptions above it probably should be whenever the controller has observed the resource
and updated the status object of the PDB with information on allowed disruptions. But currently, a PDB is
considered Current when the number of healthy replicas meets the threshold given in the PDB. Also, should
the presence of a PDB influence when a Deployment is considered Current? This would mean that a Deployment
should be considered Current whenever the number of replicas reach the threshold set by the corresponding
PDB. This is not currently supported as described below.
### Status is decided based on single resource
Currently the status of a resource is decided solely based on information from
the state of that resource. This is an issue for resources that create other resources
and that doesn't provide sufficient information within their own status object. An example
is the Service resource that doesn't provide much status information but do generate Endpoint
resources that could be used to determine status. Similar, the status of a Deployment could be
based on its generated ReplicaSets and Pods.
Not having the generated resources also limits the amount of details that can be provided
when something isn't working as expected.
## Future
### Depend on kyaml instead of k8s libraries
The sigs.k8s.io/kustomize/kstatus/status package currently depends on k8s libraries. This can be
challenging if someone wants to vendor the library within their own project. We want to replace
the dependencies on k8s libraries with kyaml for the status package. The wait package needs to
talk to a k8s cluster, so this package will continue to rely on the k8s libraries.
### Use watches instead of polling
We currently poll for updates to resources, but it would be possible to set up
watches instead. This could also be combined with deciding status based on not only a single
resource, but also all its generated resources. This would lead to a design that seems similar
to a controller, so maybe a solution like this could be built on top of controller-runtime.
A challenge here is that the rules for each built-in type would need to be expressed in a different
way that what we currently do.

View File

@@ -1,19 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
// Package kstatus contains libraries for computing status of kubernetes
// resources.
//
// Status
// Get status and/or conditions for resources based on resources already
// read from a cluster, i.e. it will not fetch resources from
// a cluster.
//
// Wait
// Get status and/or conditions for resources by fetching them
// from a cluster. This supports specifying a set of resources as
// an Inventory or as a list of manifests/unstructureds. This also
// supports polling the state of resources until they all reach a
// specific status. A common use case for this can be to wait for
// a set of resources to all finish reconciling after an apply.
package kstatus

View File

@@ -1,20 +0,0 @@
module sigs.k8s.io/kustomize/kstatus
go 1.14
require (
github.com/gogo/protobuf v1.3.1 // indirect
github.com/google/go-cmp v0.3.1 // indirect
github.com/pkg/errors v0.8.1
github.com/stretchr/testify v1.4.0
go.uber.org/atomic v1.4.0 // indirect
go.uber.org/zap v1.10.0 // indirect
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7 // indirect
golang.org/x/sys v0.0.0-20190911201528-7ad0cfa0b7b5 // indirect
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
k8s.io/api v0.17.0
k8s.io/apimachinery v0.17.0
k8s.io/client-go v0.17.0
sigs.k8s.io/controller-runtime v0.4.0
sigs.k8s.io/yaml v1.2.0
)

View File

@@ -1,419 +0,0 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0=
github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.1-coreos.6/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v3.3.15+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M=
github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680 h1:ZktWZesgun21uEDrwW7iEV1zPCGQldM2atlJZ3TdvVM=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/go-logr/logr v0.1.0 h1:M1Tv3VzNlEHg6uyACnRdtrploV2P7wZqH8BoQMtz0cg=
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
github.com/go-logr/zapr v0.1.0 h1:h+WVe9j6HAA01niTJPA/kKH0i7e0rLZBCwauQFcRE54=
github.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk=
github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk=
github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94=
github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs=
github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA=
github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64=
github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY=
github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY=
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA=
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7 h1:u4bArs140e9+AfE52mFHOXVFnOSBJBRlzTHrOPLOIhE=
github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.0.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk=
github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU=
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/go-grpc-middleware v0.0.0-20190222133341-cfaf5686ec79/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.3.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok=
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.4.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo=
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.3.0/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
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/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/soheilhy/cmux v0.1.3/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff h1:VARhShG49tiji6mdRNp7JTNDtJ0FhuprF93GBQ37xGU=
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
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/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=
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=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.uber.org/atomic v0.0.0-20181018215023-8dc6146f7569/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v0.0.0-20180814183419-67bc79d13d15/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.9.1 h1:XCJQEf3W6eZaVwhRBof6ImoYGJSITeKWsyeh3HFu/5o=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7 h1:0hQKqeLdqlt5iIwVOBErRisrHJAN57yOiPRQItI20fU=
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180112015858-5ccada7d0a7b/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180117170059-2c42eef0765b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313 h1:pczuHS43Cp2ktBEEmLwScxgjWsBSzdaQiKzUyf3DTTc=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190911201528-7ad0cfa0b7b5 h1:SW/0nsKCUaozCUtZTakri5laocGx/5bkDSSLrFUsa5s=
golang.org/x/sys v0.0.0-20190911201528-7ad0cfa0b7b5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20171227012246-e19ae1496984/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gomodules.xyz/jsonpatch/v2 v2.0.1 h1:xyiBuvkD2g5n7cYzx6u2sxQvsAy4QJsZFCzGVdzOXZ0=
gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU=
gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0=
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
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/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
k8s.io/api v0.0.0-20190918155943-95b840bb6a1f h1:8FRUST8oUkEI45WYKyD8ed7Ad0Kg5v11zHyPkEVb2xo=
k8s.io/api v0.0.0-20190918155943-95b840bb6a1f/go.mod h1:uWuOHnjmNrtQomJrvEBg0c0HRNyQ+8KTEERVsK0PW48=
k8s.io/api v0.17.0 h1:H9d/lw+VkZKEVIUc8F3wgiQ+FUXTTr21M87jXLU7yqM=
k8s.io/api v0.17.0/go.mod h1:npsyOePkeP0CPwyGfXDHxvypiYMJxBWAMpQxCaJ4ZxI=
k8s.io/apiextensions-apiserver v0.0.0-20190918161926-8f644eb6e783 h1:V6ndwCPoao1yZ52agqOKaUAl7DYWVGiXjV7ePA2i610=
k8s.io/apiextensions-apiserver v0.0.0-20190918161926-8f644eb6e783/go.mod h1:xvae1SZB3E17UpV59AWc271W/Ph25N+bjPyR63X6tPY=
k8s.io/apimachinery v0.0.0-20190913080033-27d36303b655 h1:CS1tBQz3HOXiseWZu6ZicKX361CZLT97UFnnPx0aqBw=
k8s.io/apimachinery v0.0.0-20190913080033-27d36303b655/go.mod h1:nL6pwRT8NgfF8TT68DBI8uEePRt89cSvoXUVqbkWHq4=
k8s.io/apimachinery v0.17.0 h1:xRBnuie9rXcPxUkDizUsGvPf1cnlZCFu210op7J7LJo=
k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
k8s.io/apiserver v0.0.0-20190918160949-bfa5e2e684ad/go.mod h1:XPCXEwhjaFN29a8NldXA901ElnKeKLrLtREO9ZhFyhg=
k8s.io/client-go v0.0.0-20190918160344-1fbdaa4c8d90 h1:mLmhKUm1X+pXu0zXMEzNsOF5E2kKFGe5o6BZBIIqA6A=
k8s.io/client-go v0.0.0-20190918160344-1fbdaa4c8d90/go.mod h1:J69/JveO6XESwVgG53q3Uz5OSfgsv4uxpScmmyYOOlk=
k8s.io/client-go v0.17.0 h1:8QOGvUGdqDMFrm9sD6IUFl256BcffynGoe80sxgTEDg=
k8s.io/client-go v0.17.0/go.mod h1:TYgR6EUHs6k45hb6KWjVD6jFZvJV4gHDikv/It0xz+k=
k8s.io/code-generator v0.0.0-20190912054826-cd179ad6a269/go.mod h1:V5BD6M4CyaN5m+VthcclXWsVcT1Hu+glwa1bi3MIsyE=
k8s.io/component-base v0.0.0-20190918160511-547f6c5d7090/go.mod h1:933PBGtQFJky3TEwYx4aEPZ4IxqhWh3R6DCmzqIn1hA=
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v0.4.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a h1:UcxjrRMyNx/i/y8G7kPvLyy7rfbeuf1PYyBf973pgyU=
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
k8s.io/utils v0.0.0-20191114184206-e782cd3c129f h1:GiPwtSzdP43eI1hpPCbROQCCIgCuiMMNF8YUVLF3vJo=
k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw=
modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=
modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=
modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs=
modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I=
sigs.k8s.io/controller-runtime v0.4.0 h1:wATM6/m+3w8lj8FXNaO6Fs/rq/vqoOjO1Q116Z9NPsg=
sigs.k8s.io/controller-runtime v0.4.0/go.mod h1:ApC79lpY3PHW9xj/w9pj+lYkLgwAAUZwfXkME1Lajns=
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca/go.mod h1:IIgPezJWb76P0hotTxzDbWsMYB8APh18qZnxkomBpxA=
sigs.k8s.io/testing_frameworks v0.1.2 h1:vK0+tvjF0BZ/RYFeZ1E6BYBwHJJXhjuZ3TdsEKH+UQM=
sigs.k8s.io/testing_frameworks v0.1.2/go.mod h1:ToQrwSC3s8Xf/lADdZp3Mktcql9CG0UAmdJG9th5i0w=
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=

View File

@@ -1,497 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package status
import (
"fmt"
"math"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
// GetConditionsFn defines the signature for functions to compute the
// status of a built-in resource.
type GetConditionsFn func(*unstructured.Unstructured) (*Result, error)
// legacyTypes defines the mapping from GroupKind to a function that can
// compute the status for the given resource.
var legacyTypes = map[string]GetConditionsFn{
"Service": serviceConditions,
"Pod": podConditions,
"Secret": alwaysReady,
"PersistentVolumeClaim": pvcConditions,
"apps/StatefulSet": stsConditions,
"apps/DaemonSet": daemonsetConditions,
"extensions/DaemonSet": daemonsetConditions,
"apps/Deployment": deploymentConditions,
"extensions/Deployment": deploymentConditions,
"apps/ReplicaSet": replicasetConditions,
"extensions/ReplicaSet": replicasetConditions,
"policy/PodDisruptionBudget": pdbConditions,
"batch/CronJob": alwaysReady,
"ConfigMap": alwaysReady,
"batch/Job": jobConditions,
}
const (
tooFewReady = "LessReady"
tooFewAvailable = "LessAvailable"
tooFewUpdated = "LessUpdated"
tooFewReplicas = "LessReplicas"
onDeleteUpdateStrategy = "OnDelete"
)
// GetLegacyConditionsFn returns a function that can compute the status for the
// given resource, or nil if the resource type is not known.
func GetLegacyConditionsFn(u *unstructured.Unstructured) GetConditionsFn {
gvk := u.GroupVersionKind()
g := gvk.Group
k := gvk.Kind
key := g + "/" + k
if g == "" {
key = k
}
return legacyTypes[key]
}
// alwaysReady Used for resources that are always ready
func alwaysReady(u *unstructured.Unstructured) (*Result, error) {
return &Result{
Status: CurrentStatus,
Message: "Resource is always ready",
Conditions: []Condition{},
}, nil
}
// stsConditions return standardized Conditions for Statefulset
//
// StatefulSet does define the .status.conditions property, but the controller never
// actually sets any Conditions. Thus, status must be computed only based on the other
// properties under .status. We don't have any way to find out if a reconcile for a
// StatefulSet has failed.
func stsConditions(u *unstructured.Unstructured) (*Result, error) {
obj := u.UnstructuredContent()
// updateStrategy==ondelete is a user managed statefulset.
updateStrategy := GetStringField(obj, ".spec.updateStrategy.type", "")
if updateStrategy == onDeleteUpdateStrategy {
return &Result{
Status: CurrentStatus,
Message: "StatefulSet is using the ondelete update strategy",
Conditions: []Condition{},
}, nil
}
// Replicas
specReplicas := GetIntField(obj, ".spec.replicas", 1)
readyReplicas := GetIntField(obj, ".status.readyReplicas", 0)
currentReplicas := GetIntField(obj, ".status.currentReplicas", 0)
updatedReplicas := GetIntField(obj, ".status.updatedReplicas", 0)
statusReplicas := GetIntField(obj, ".status.replicas", 0)
partition := GetIntField(obj, ".spec.updateStrategy.rollingUpdate.partition", -1)
if specReplicas > statusReplicas {
message := fmt.Sprintf("Replicas: %d/%d", statusReplicas, specReplicas)
return newInProgressStatus(tooFewReplicas, message), nil
}
if specReplicas > readyReplicas {
message := fmt.Sprintf("Ready: %d/%d", readyReplicas, specReplicas)
return newInProgressStatus(tooFewReady, message), nil
}
// https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#partitions
if partition != -1 {
if updatedReplicas < (specReplicas - partition) {
message := fmt.Sprintf("updated: %d/%d", updatedReplicas, specReplicas-partition)
return newInProgressStatus("PartitionRollout", message), nil
}
// Partition case All ok
return &Result{
Status: CurrentStatus,
Message: fmt.Sprintf("Partition rollout complete. updated: %d", updatedReplicas),
Conditions: []Condition{},
}, nil
}
if specReplicas > currentReplicas {
message := fmt.Sprintf("current: %d/%d", currentReplicas, specReplicas)
return newInProgressStatus("LessCurrent", message), nil
}
// Revision
currentRevision := GetStringField(obj, ".status.currentRevision", "")
updatedRevision := GetStringField(obj, ".status.updateRevision", "")
if currentRevision != updatedRevision {
message := "Waiting for updated revision to match current"
return newInProgressStatus("RevisionMismatch", message), nil
}
// All ok
return &Result{
Status: CurrentStatus,
Message: fmt.Sprintf("All replicas scheduled as expected. Replicas: %d", statusReplicas),
Conditions: []Condition{},
}, nil
}
// deploymentConditions return standardized Conditions for Deployment.
//
// For Deployments, we look at .status.conditions as well as the other properties
// under .status. Status will be Failed if the progress deadline has been exceeded.
func deploymentConditions(u *unstructured.Unstructured) (*Result, error) {
obj := u.UnstructuredContent()
progressing := false
// Check if progressDeadlineSeconds is set. If not, the controller will not set
// the `Progressing` condition, so it will always consider a deployment to be
// progressing. The use of math.MaxInt32 is due to special handling in the
// controller:
// https://github.com/kubernetes/kubernetes/blob/a3ccea9d8743f2ff82e41b6c2af6dc2c41dc7b10/pkg/controller/deployment/util/deployment_util.go#L886
progressDeadline := GetIntField(obj, ".spec.progressDeadlineSeconds", math.MaxInt32)
if progressDeadline == math.MaxInt32 {
progressing = true
}
available := false
objc, err := GetObjectWithConditions(obj)
if err != nil {
return nil, err
}
for _, c := range objc.Status.Conditions {
switch c.Type {
case "Progressing": //appsv1.DeploymentProgressing:
// https://github.com/kubernetes/kubernetes/blob/a3ccea9d8743f2ff82e41b6c2af6dc2c41dc7b10/pkg/controller/deployment/progress.go#L52
if c.Reason == "ProgressDeadlineExceeded" {
return &Result{
Status: FailedStatus,
Message: "Progress deadline exceeded",
Conditions: []Condition{{ConditionFailed, corev1.ConditionTrue, c.Reason, c.Message}},
}, nil
}
if c.Status == corev1.ConditionTrue && c.Reason == "NewReplicaSetAvailable" {
progressing = true
}
case "Available": //appsv1.DeploymentAvailable:
if c.Status == corev1.ConditionTrue {
available = true
}
}
}
// replicas
specReplicas := GetIntField(obj, ".spec.replicas", 1) // Controller uses 1 as default if not specified.
statusReplicas := GetIntField(obj, ".status.replicas", 0)
updatedReplicas := GetIntField(obj, ".status.updatedReplicas", 0)
readyReplicas := GetIntField(obj, ".status.readyReplicas", 0)
availableReplicas := GetIntField(obj, ".status.availableReplicas", 0)
// TODO spec.replicas zero case ??
if specReplicas > statusReplicas {
message := fmt.Sprintf("replicas: %d/%d", statusReplicas, specReplicas)
return newInProgressStatus(tooFewReplicas, message), nil
}
if specReplicas > updatedReplicas {
message := fmt.Sprintf("Updated: %d/%d", updatedReplicas, specReplicas)
return newInProgressStatus(tooFewUpdated, message), nil
}
if statusReplicas > updatedReplicas {
message := fmt.Sprintf("Pending termination: %d", statusReplicas-updatedReplicas)
return newInProgressStatus("ExtraPods", message), nil
}
if updatedReplicas > availableReplicas {
message := fmt.Sprintf("Available: %d/%d", availableReplicas, updatedReplicas)
return newInProgressStatus(tooFewAvailable, message), nil
}
if specReplicas > readyReplicas {
message := fmt.Sprintf("Ready: %d/%d", readyReplicas, specReplicas)
return newInProgressStatus(tooFewReady, message), nil
}
// check conditions
if !progressing {
message := "ReplicaSet not Available"
return newInProgressStatus("ReplicaSetNotAvailable", message), nil
}
if !available {
message := "Deployment not Available"
return newInProgressStatus("DeploymentNotAvailable", message), nil
}
// All ok
return &Result{
Status: CurrentStatus,
Message: fmt.Sprintf("Deployment is available. Replicas: %d", statusReplicas),
Conditions: []Condition{},
}, nil
}
// replicasetConditions return standardized Conditions for Replicaset
func replicasetConditions(u *unstructured.Unstructured) (*Result, error) {
obj := u.UnstructuredContent()
// Conditions
objc, err := GetObjectWithConditions(obj)
if err != nil {
return nil, err
}
for _, c := range objc.Status.Conditions {
// https://github.com/kubernetes/kubernetes/blob/a3ccea9d8743f2ff82e41b6c2af6dc2c41dc7b10/pkg/controller/replicaset/replica_set_utils.go
if c.Type == "ReplicaFailure" && c.Status == corev1.ConditionTrue {
message := "Replica Failure condition. Check Pods"
return newInProgressStatus("ReplicaFailure", message), nil
}
}
// Replicas
specReplicas := GetIntField(obj, ".spec.replicas", 1) // Controller uses 1 as default if not specified.
statusReplicas := GetIntField(obj, ".status.replicas", 0)
readyReplicas := GetIntField(obj, ".status.readyReplicas", 0)
availableReplicas := GetIntField(obj, ".status.availableReplicas", 0)
fullyLabelledReplicas := GetIntField(obj, ".status.fullyLabeledReplicas", 0)
if specReplicas > fullyLabelledReplicas {
message := fmt.Sprintf("Labelled: %d/%d", fullyLabelledReplicas, specReplicas)
return newInProgressStatus("LessLabelled", message), nil
}
if specReplicas > availableReplicas {
message := fmt.Sprintf("Available: %d/%d", availableReplicas, specReplicas)
return newInProgressStatus(tooFewAvailable, message), nil
}
if specReplicas > readyReplicas {
message := fmt.Sprintf("Ready: %d/%d", readyReplicas, specReplicas)
return newInProgressStatus(tooFewReady, message), nil
}
if specReplicas < statusReplicas {
message := fmt.Sprintf("replicas: %d/%d", statusReplicas, specReplicas)
return newInProgressStatus("ExtraPods", message), nil
}
// All ok
return &Result{
Status: CurrentStatus,
Message: fmt.Sprintf("ReplicaSet is available. Replicas: %d", statusReplicas),
Conditions: []Condition{},
}, nil
}
// daemonsetConditions return standardized Conditions for DaemonSet
func daemonsetConditions(u *unstructured.Unstructured) (*Result, error) {
obj := u.UnstructuredContent()
// replicas
desiredNumberScheduled := GetIntField(obj, ".status.desiredNumberScheduled", -1)
currentNumberScheduled := GetIntField(obj, ".status.currentNumberScheduled", 0)
updatedNumberScheduled := GetIntField(obj, ".status.updatedNumberScheduled", 0)
numberAvailable := GetIntField(obj, ".status.numberAvailable", 0)
numberReady := GetIntField(obj, ".status.numberReady", 0)
if desiredNumberScheduled == -1 {
message := "Missing .status.desiredNumberScheduled"
return newInProgressStatus("NoDesiredNumber", message), nil
}
if desiredNumberScheduled > currentNumberScheduled {
message := fmt.Sprintf("Current: %d/%d", currentNumberScheduled, desiredNumberScheduled)
return newInProgressStatus("LessCurrent", message), nil
}
if desiredNumberScheduled > updatedNumberScheduled {
message := fmt.Sprintf("Updated: %d/%d", updatedNumberScheduled, desiredNumberScheduled)
return newInProgressStatus(tooFewUpdated, message), nil
}
if desiredNumberScheduled > numberAvailable {
message := fmt.Sprintf("Available: %d/%d", numberAvailable, desiredNumberScheduled)
return newInProgressStatus(tooFewAvailable, message), nil
}
if desiredNumberScheduled > numberReady {
message := fmt.Sprintf("Ready: %d/%d", numberReady, desiredNumberScheduled)
return newInProgressStatus(tooFewReady, message), nil
}
// All ok
return &Result{
Status: CurrentStatus,
Message: fmt.Sprintf("All replicas scheduled as expected. Replicas: %d", desiredNumberScheduled),
Conditions: []Condition{},
}, nil
}
// pvcConditions return standardized Conditions for PVC
func pvcConditions(u *unstructured.Unstructured) (*Result, error) {
obj := u.UnstructuredContent()
phase := GetStringField(obj, ".status.phase", "unknown")
if phase != "Bound" { // corev1.ClaimBound
message := fmt.Sprintf("PVC is not Bound. phase: %s", phase)
return newInProgressStatus("NotBound", message), nil
}
// All ok
return &Result{
Status: CurrentStatus,
Message: "PVC is Bound",
Conditions: []Condition{},
}, nil
}
// podConditions return standardized Conditions for Pod
func podConditions(u *unstructured.Unstructured) (*Result, error) {
obj := u.UnstructuredContent()
objc, err := GetObjectWithConditions(obj)
if err != nil {
return nil, err
}
phase := GetStringField(obj, ".status.phase", "unknown")
if phase == "Succeeded" {
return &Result{
Status: CurrentStatus,
Message: "Pod has completed successfully",
Conditions: []Condition{},
}, nil
}
for _, c := range objc.Status.Conditions {
if c.Type == "Ready" {
if c.Status == corev1.ConditionTrue {
return &Result{
Status: CurrentStatus,
Message: "Pod has reached the ready state",
Conditions: []Condition{},
}, nil
}
if c.Status == corev1.ConditionFalse && c.Reason == "PodCompleted" && phase != "Succeeded" {
message := "Pod has completed, but not successfully."
return &Result{
Status: FailedStatus,
Message: message,
Conditions: []Condition{{
Type: ConditionFailed,
Status: corev1.ConditionTrue,
Reason: "PodFailed",
Message: fmt.Sprintf("Pod has completed, but not succeesfully."),
}},
}, nil
}
}
}
message := "Pod has not become ready"
return newInProgressStatus("PodNotReady", message), nil
}
// pdbConditions computes the status for PodDisruptionBudgets. A PDB
// is currently considered Current if the disruption controller has
// observed the latest version of the PDB resource and has computed
// the AllowedDisruptions. PDBs do have ObservedGeneration in the
// Status object, so if this function gets called we know that
// the controller has observed the latest changes.
// The disruption controller does not set any conditions if
// computing the AllowedDisruptions fails (and there are many ways
// it can fail), but there is PR against OSS Kubernetes to address
// this: https://github.com/kubernetes/kubernetes/pull/86929
func pdbConditions(u *unstructured.Unstructured) (*Result, error) {
// All ok
return &Result{
Status: CurrentStatus,
Message: "AllowedDisruptions has been computed.",
Conditions: []Condition{},
}, nil
}
// jobConditions return standardized Conditions for Job
//
// A job will have the InProgress status until it starts running. Then it will have the Current
// status while the job is running and after it has been completed successfully. It
// will have the Failed status if it the job has failed.
func jobConditions(u *unstructured.Unstructured) (*Result, error) {
obj := u.UnstructuredContent()
parallelism := GetIntField(obj, ".spec.parallelism", 1)
completions := GetIntField(obj, ".spec.completions", parallelism)
succeeded := GetIntField(obj, ".status.succeeded", 0)
active := GetIntField(obj, ".status.active", 0)
failed := GetIntField(obj, ".status.failed", 0)
starttime := GetStringField(obj, ".status.startTime", "")
// Conditions
// https://github.com/kubernetes/kubernetes/blob/master/pkg/controller/job/utils.go#L24
objc, err := GetObjectWithConditions(obj)
if err != nil {
return nil, err
}
for _, c := range objc.Status.Conditions {
switch c.Type {
case "Complete":
if c.Status == corev1.ConditionTrue {
message := fmt.Sprintf("Job Completed. succeeded: %d/%d", succeeded, completions)
return &Result{
Status: CurrentStatus,
Message: message,
Conditions: []Condition{},
}, nil
}
case "Failed":
if c.Status == corev1.ConditionTrue {
message := fmt.Sprintf("Job Failed. failed: %d/%d", failed, completions)
return &Result{
Status: FailedStatus,
Message: message,
Conditions: []Condition{{
ConditionFailed,
corev1.ConditionTrue,
"JobFailed",
fmt.Sprintf("Job Failed. failed: %d/%d", failed, completions),
}},
}, nil
}
}
}
// replicas
if starttime == "" {
message := "Job not started"
return newInProgressStatus("JobNotStarted", message), nil
}
return &Result{
Status: CurrentStatus,
Message: fmt.Sprintf("Job in progress. success:%d, active: %d, failed: %d", succeeded, active, failed),
Conditions: []Condition{},
}, nil
}
// serviceConditions return standardized Conditions for Service
func serviceConditions(u *unstructured.Unstructured) (*Result, error) {
obj := u.UnstructuredContent()
specType := GetStringField(obj, ".spec.type", "ClusterIP")
specClusterIP := GetStringField(obj, ".spec.clusterIP", "")
if specType == "LoadBalancer" {
if specClusterIP == "" {
message := "ClusterIP not set. Service type: LoadBalancer"
return newInProgressStatus("NoIPAssigned", message), nil
}
}
return &Result{
Status: CurrentStatus,
Message: "Service is ready",
Conditions: []Condition{},
}, nil
}

View File

@@ -1,41 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
// Package kstatus contains functionality for computing the status
// of Kubernetes resources.
//
// The statuses defined in this package are:
// * InProgress
// * Current
// * Failed
// * Terminating
// * Unknown
//
// Computing the status of a resources can be done by calling the
// Compute function in the status package.
//
// import (
// "sigs.k8s.io/kustomize/kstatus/status"
// )
//
// res, err := status.Compute(resource)
//
// The package also defines a set of new conditions:
// * InProgress
// * Failed
// These conditions have been chosen to follow the
// "abnormal-true" pattern where conditions should be set to true
// for error/abnormal conditions and the absence of a condition means
// things are normal.
//
// The Augment function augments any unstructured resource with
// the standard conditions described above. The values of
// these conditions are decided based on other status information
// available in the resources.
//
// import (
// "sigs.k8s.io/kustomize/kstatus/status
// )
//
// err := status.Augment(resource)
package status

View File

@@ -1,110 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package status_test
import (
"fmt"
"log"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
. "sigs.k8s.io/kustomize/kstatus/status"
"sigs.k8s.io/yaml"
)
func ExampleCompute() {
deploymentManifest := `
apiVersion: apps/v1
kind: Deployment
metadata:
name: test
generation: 1
namespace: qual
status:
observedGeneration: 1
updatedReplicas: 1
readyReplicas: 1
availableReplicas: 1
replicas: 1
conditions:
- type: Progressing
status: "True"
reason: NewReplicaSetAvailable
- type: Available
status: "True"
`
deployment := yamlManifestToUnstructured(deploymentManifest)
res, err := Compute(deployment)
if err != nil {
log.Fatal(err)
}
fmt.Println(res.Status)
// Output:
// Current
}
func ExampleAugment() {
deploymentManifest := `
apiVersion: apps/v1
kind: Deployment
metadata:
name: test
generation: 1
namespace: qual
status:
observedGeneration: 1
updatedReplicas: 1
readyReplicas: 1
availableReplicas: 1
replicas: 1
conditions:
- type: Progressing
status: "True"
reason: NewReplicaSetAvailable
- type: Available
status: "True"
`
deployment := yamlManifestToUnstructured(deploymentManifest)
err := Augment(deployment)
if err != nil {
log.Fatal(err)
}
b, err := yaml.Marshal(deployment.Object)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(b))
// Output:
// apiVersion: apps/v1
// kind: Deployment
// metadata:
// generation: 1
// name: test
// namespace: qual
// status:
// availableReplicas: 1
// conditions:
// - reason: NewReplicaSetAvailable
// status: "True"
// type: Progressing
// - status: "True"
// type: Available
// observedGeneration: 1
// readyReplicas: 1
// replicas: 1
// updatedReplicas: 1
}
func yamlManifestToUnstructured(manifest string) *unstructured.Unstructured {
jsonManifest, err := yaml.YAMLToJSON([]byte(manifest))
if err != nil {
log.Fatal(err)
}
resource, _, err := unstructured.UnstructuredJSONScheme.Decode(jsonManifest, nil, nil)
if err != nil {
log.Fatal(err)
}
return resource.(*unstructured.Unstructured)
}

View File

@@ -1,100 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package status
import (
"fmt"
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
// checkGenericProperties looks at the properties that are available on
// all or most of the Kubernetes resources. If a decision can be made based
// on this information, there is no need to look at the resource-specidic
// rules.
// This also checks for the presence of the conditions defined in this package.
// If any of these are set on the resource, a decision is made solely based
// on this and none of the resource specific rules will be used. The goal here
// is that if controllers, built-in or custom, use these conditions, we can easily
// find status of resources.
func checkGenericProperties(u *unstructured.Unstructured) (*Result, error) {
obj := u.UnstructuredContent()
// Check if the resource is scheduled for deletion
deletionTimestamp, found, err := unstructured.NestedString(obj, "metadata", "deletionTimestamp")
if err != nil {
return nil, errors.Wrap(err, "looking up metadata.deletionTimestamp from resource")
}
if found && deletionTimestamp != "" {
return &Result{
Status: TerminatingStatus,
Message: "Resource scheduled for deletion",
Conditions: []Condition{},
}, nil
}
res, err := checkGeneration(u)
if res != nil || err != nil {
return res, err
}
// Check if the resource has any of the standard conditions. If so, we just use them
// and no need to look at anything else.
objWithConditions, err := GetObjectWithConditions(obj)
if err != nil {
return nil, err
}
for _, cond := range objWithConditions.Status.Conditions {
if cond.Type == string(ConditionInProgress) && cond.Status == corev1.ConditionTrue {
return newInProgressStatus(cond.Reason, cond.Message), nil
}
if cond.Type == string(ConditionFailed) && cond.Status == corev1.ConditionTrue {
return &Result{
Status: FailedStatus,
Message: cond.Message,
Conditions: []Condition{
{
Type: ConditionFailed,
Status: corev1.ConditionTrue,
Reason: cond.Reason,
Message: cond.Message,
},
},
}, nil
}
}
return nil, nil
}
func checkGeneration(u *unstructured.Unstructured) (*Result, error) {
// ensure that the meta generation is observed
generation, found, err := unstructured.NestedInt64(u.Object, "metadata", "generation")
if err != nil {
return nil, errors.Wrap(err, "looking up metadata.generation from resource")
}
if !found {
return nil, nil
}
observedGeneration, found, err := unstructured.NestedInt64(u.Object, "status", "observedGeneration")
if err != nil {
return nil, errors.Wrap(err, "looking up status.observedGeneration from resource")
}
if found {
// Resource does not have this field, so we can't do this check.
// TODO(mortent): Verify behavior of not set vs does not exist.
if observedGeneration != generation {
message := fmt.Sprintf("%s generation is %d, but latest observed generation is %d", u.GetKind(), generation, observedGeneration)
return &Result{
Status: InProgressStatus,
Message: message,
Conditions: []Condition{newInProgressCondition("LatestGenerationNotObserved", message)},
}, nil
}
}
return nil, nil
}

View File

@@ -1,187 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package status
import (
"fmt"
"time"
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
const (
// The set of standard conditions defined in this package. These follow the "abnormality-true"
// convention where conditions should have a true value for abnormal/error situations and the absence
// of a condition should be interpreted as a false value, i.e. everything is normal.
ConditionFailed ConditionType = "Failed"
ConditionInProgress ConditionType = "InProgress"
// The set of status conditions which can be assigned to resources.
InProgressStatus Status = "InProgress"
FailedStatus Status = "Failed"
CurrentStatus Status = "Current"
TerminatingStatus Status = "Terminating"
UnknownStatus Status = "Unknown"
)
var (
Statuses = []Status{InProgressStatus, FailedStatus, CurrentStatus, TerminatingStatus, UnknownStatus}
ConditionTypes = []ConditionType{ConditionFailed, ConditionInProgress}
)
// ConditionType defines the set of condition types allowed inside a Condition struct.
type ConditionType string
// String returns the ConditionType as a string.
func (c ConditionType) String() string {
return string(c)
}
// Status defines the set of statuses a resource can have.
type Status string
// String returns the status as a string.
func (s Status) String() string {
return string(s)
}
// StatusFromString turns a string into a Status. Will panic if the provided string is
// not a valid status.
func FromStringOrDie(text string) Status {
s := Status(text)
for _, r := range Statuses {
if s == r {
return s
}
}
panic(fmt.Errorf("string has invalid status: %s", s))
}
// Result contains the results of a call to compute the status of
// a resource.
type Result struct {
//Status
Status Status
// Message
Message string
// Conditions list of extracted conditions from Resource
Conditions []Condition
}
// Condition defines the general format for conditions on Kubernetes resources.
// In practice, each kubernetes resource defines their own format for conditions, but
// most (maybe all) follows this structure.
type Condition struct {
// Type condition type
Type ConditionType
// Status String that describes the condition status
Status corev1.ConditionStatus
// Reason one work CamelCase reason
Reason string
// Message Human readable reason string
Message string
}
// Compute finds the status of a given unstructured resource. It does not
// fetch the state of the resource from a cluster, so the provided unstructured
// must have the complete state, including status.
//
// The returned result contains the status of the resource, which will be
// one of
// * InProgress
// * Current
// * Failed
// * Terminating
// It also contains a message that provides more information on why
// the resource has the given status. Finally, the result also contains
// a list of standard resources that would belong on the given resource.
func Compute(u *unstructured.Unstructured) (*Result, error) {
res, err := checkGenericProperties(u)
if err != nil {
return nil, err
}
// If res is not nil, it means the generic checks was able to determine
// the status of the resource. We don't need to check the type-specific
// rules.
if res != nil {
return res, nil
}
fn := GetLegacyConditionsFn(u)
if fn != nil {
return fn(u)
}
// The resource is not one of the built-in types with specific
// rules and we were unable to make a decision based on the
// generic rules. In this case we assume that the absence of any known
// conditions means the resource is current.
return &Result{
Status: CurrentStatus,
Message: "Resource is current",
Conditions: []Condition{},
}, err
}
// Augment takes a resource and augments the resource with the
// standard status conditions.
func Augment(u *unstructured.Unstructured) error {
res, err := Compute(u)
if err != nil {
return err
}
conditions, found, err := unstructured.NestedSlice(u.Object, "status", "conditions")
if err != nil {
return err
}
if !found {
conditions = make([]interface{}, 0)
}
currentTime := time.Now().UTC().Format(time.RFC3339)
for _, resCondition := range res.Conditions {
present := false
for _, c := range conditions {
condition, ok := c.(map[string]interface{})
if !ok {
return errors.New("condition does not have the expected structure")
}
conditionType, ok := condition["type"].(string)
if !ok {
return errors.New("condition type does not have the expected type")
}
if conditionType == string(resCondition.Type) {
conditionStatus, ok := condition["status"].(string)
if !ok {
return errors.New("condition status does not have the expected type")
}
if conditionStatus != string(resCondition.Status) {
condition["lastTransitionTime"] = currentTime
}
condition["status"] = string(resCondition.Status)
condition["lastUpdateTime"] = currentTime
condition["reason"] = resCondition.Reason
condition["message"] = resCondition.Message
present = true
}
}
if !present {
conditions = append(conditions, map[string]interface{}{
"lastTransitionTime": currentTime,
"lastUpdateTime": currentTime,
"message": resCondition.Message,
"reason": resCondition.Reason,
"status": string(resCondition.Status),
"type": string(resCondition.Type),
})
}
}
return unstructured.SetNestedSlice(u.Object, conditions, "status", "conditions")
}

View File

@@ -1,166 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package status
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
var pod = `
apiVersion: v1
kind: Pod
metadata:
generation: 1
name: test
namespace: qual
status:
phase: Running
`
var custom = `
apiVersion: v1beta1
kind: SomeCustomKind
metadata:
generation: 1
name: test
namespace: default
`
var timestamp = time.Now().Add(-1 * time.Minute).UTC().Format(time.RFC3339)
func addConditions(t *testing.T, u *unstructured.Unstructured, conditions []map[string]interface{}) {
conds := make([]interface{}, 0)
for _, c := range conditions {
conds = append(conds, c)
}
err := unstructured.SetNestedSlice(u.Object, conds, "status", "conditions")
if err != nil {
t.Fatal(err)
}
}
func TestAugmentConditions(t *testing.T) {
testCases := map[string]struct {
manifest string
withConditions []map[string]interface{}
expectedConditions []Condition
}{
"no existing conditions": {
manifest: pod,
withConditions: []map[string]interface{}{},
expectedConditions: []Condition{
{
Type: ConditionInProgress,
Status: corev1.ConditionTrue,
Reason: "PodNotReady",
},
},
},
"has other existing conditions": {
manifest: pod,
withConditions: []map[string]interface{}{
{
"lastTransitionTime": timestamp,
"lastUpdateTime": timestamp,
"type": "Ready",
"status": "False",
"reason": "Pod has not started",
},
},
expectedConditions: []Condition{
{
Type: ConditionInProgress,
Status: corev1.ConditionTrue,
Reason: "PodNotReady",
},
{
Type: "Ready",
Status: corev1.ConditionFalse,
Reason: "Pod has not started",
},
},
},
"already has condition of standard type InProgress": {
manifest: pod,
withConditions: []map[string]interface{}{
{
"lastTransitionTime": timestamp,
"lastUpdateTime": timestamp,
"type": ConditionInProgress.String(),
"status": "True",
"reason": "PodIsAbsolutelyNotReady",
},
},
expectedConditions: []Condition{
{
Type: ConditionInProgress,
Status: corev1.ConditionTrue,
Reason: "PodIsAbsolutelyNotReady",
},
},
},
"already has condition of standard type Failed": {
manifest: pod,
withConditions: []map[string]interface{}{
{
"lastTransitionTime": timestamp,
"lastUpdateTime": timestamp,
"type": ConditionFailed.String(),
"status": "True",
"reason": "PodHasFailed",
},
},
expectedConditions: []Condition{
{
Type: ConditionFailed,
Status: corev1.ConditionTrue,
Reason: "PodHasFailed",
},
},
},
"custom resource with no conditions": {
manifest: custom,
withConditions: []map[string]interface{}{},
expectedConditions: []Condition{},
},
}
for tn, tc := range testCases {
tc := tc
t.Run(tn, func(t *testing.T) {
u := y2u(t, tc.manifest)
addConditions(t, u, tc.withConditions)
err := Augment(u)
if err != nil {
t.Error(err)
}
obj, err := GetObjectWithConditions(u.Object)
if err != nil {
t.Error(err)
}
assert.Equal(t, len(tc.expectedConditions), len(obj.Status.Conditions))
for _, expectedCondition := range tc.expectedConditions {
found := false
for _, condition := range obj.Status.Conditions {
if expectedCondition.Type.String() != condition.Type {
continue
}
found = true
assert.Equal(t, expectedCondition.Type.String(), condition.Type)
assert.Equal(t, expectedCondition.Reason, condition.Reason)
}
assert.True(t, found)
}
})
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,112 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package status
import (
"strings"
corev1 "k8s.io/api/core/v1"
apiunstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
)
// newInProgressCondition creates an inProgress condition with the given
// reason and message.
func newInProgressCondition(reason, message string) Condition {
return Condition{
Type: ConditionInProgress,
Status: corev1.ConditionTrue,
Reason: reason,
Message: message,
}
}
// newInProgressStatus creates a status Result with the InProgress status
// and an InProgress condition.
func newInProgressStatus(reason, message string) *Result {
return &Result{
Status: InProgressStatus,
Message: message,
Conditions: []Condition{newInProgressCondition(reason, message)},
}
}
// ObjWithConditions Represent meta object with status.condition array
type ObjWithConditions struct {
// Status as expected to be present in most compliant kubernetes resources
Status ConditionStatus `json:"status" yaml:"status"`
}
// ConditionStatus represent status with condition array
type ConditionStatus struct {
// Array of Conditions as expected to be present in kubernetes resources
Conditions []BasicCondition `json:"conditions" yaml:"conditions"`
}
// BasicCondition fields that are expected in a condition
type BasicCondition struct {
// Type Condition type
Type string `json:"type" yaml:"type"`
// Status is one of True,False,Unknown
Status corev1.ConditionStatus `json:"status" yaml:"status"`
// Reason simple single word reason in CamleCase
// +optional
Reason string `json:"reason,omitempty" yaml:"reason"`
// Message human readable reason
// +optional
Message string `json:"message,omitempty" yaml:"message"`
}
// GetObjectWithConditions return typed object
func GetObjectWithConditions(in map[string]interface{}) (*ObjWithConditions, error) {
var out = new(ObjWithConditions)
err := runtime.DefaultUnstructuredConverter.FromUnstructured(in, out)
if err != nil {
return nil, err
}
return out, nil
}
// GetStringField return field as string defaulting to value if not found
func GetStringField(obj map[string]interface{}, fieldPath string, defaultValue string) string {
var rv = defaultValue
fields := strings.Split(fieldPath, ".")
if fields[0] == "" {
fields = fields[1:]
}
val, found, err := apiunstructured.NestedFieldNoCopy(obj, fields...)
if !found || err != nil {
return rv
}
if v, ok := val.(string); ok {
return v
}
return rv
}
// GetIntField return field as string defaulting to value if not found
func GetIntField(obj map[string]interface{}, fieldPath string, defaultValue int) int {
fields := strings.Split(fieldPath, ".")
if fields[0] == "" {
fields = fields[1:]
}
val, found, err := apiunstructured.NestedFieldNoCopy(obj, fields...)
if !found || err != nil {
return defaultValue
}
switch v := val.(type) {
case int:
return v
case int32:
return int(v)
case int64:
return int(v)
}
return defaultValue
}

View File

@@ -1,59 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package status
import (
"testing"
"github.com/stretchr/testify/assert"
)
var testObj = map[string]interface{}{
"f1": map[string]interface{}{
"f2": map[string]interface{}{
"i32": int32(32),
"i64": int64(64),
"float": 64.02,
"ms": []interface{}{
map[string]interface{}{"f1f2ms0f1": 22},
map[string]interface{}{"f1f2ms1f1": "index1"},
},
"msbad": []interface{}{
map[string]interface{}{"f1f2ms0f1": 22},
32,
},
},
},
"ride": "dragon",
"status": map[string]interface{}{
"conditions": []interface{}{
map[string]interface{}{"f1f2ms0f1": 22},
map[string]interface{}{"f1f2ms1f1": "index1"},
},
},
}
func TestGetIntField(t *testing.T) {
v := GetIntField(testObj, ".f1.f2.i32", -1)
assert.Equal(t, int(32), v)
v = GetIntField(testObj, ".f1.f2.wrongname", -1)
assert.Equal(t, int(-1), v)
v = GetIntField(testObj, ".f1.f2.i64", -1)
assert.Equal(t, int(64), v)
v = GetIntField(testObj, ".f1.f2.float", -1)
assert.Equal(t, int(-1), v)
}
func TestGetStringField(t *testing.T) {
v := GetStringField(testObj, ".ride", "horse")
assert.Equal(t, v, "dragon")
v = GetStringField(testObj, ".destination", "north")
assert.Equal(t, v, "north")
}

View File

@@ -1,58 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
// Package wait contains functionality for getting the statuses
// of a list of kubernetes resources. Unlike the status package,
// the functions exposed in the wait package will talk to a
// live kubernetes cluster to get the latest state of resources
// and provides functionality for polling the cluster until the
// resources reach the Current status.
//
// FetchAndResolve will fetch resources from a cluster, compute the
// status for each of them and then return the results. The list of
// resources is defined as a slice of ResourceIdentifier, which is
// an interface that is implemented by the Unstructured type. It
// only requires functions for getting the apiVersion, kind, name
// and namespace of a resource.
//
// import (
// "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
// "k8s.io/apimachinery/pkg/types"
// "sigs.k8s.io/kustomize/kstatus/wait"
// )
//
// key := types.NamespacedName{Name: "name", Namespace: "namespace"}
// deployment := &unstructured.Unstructured{
// Object: map[string]interface{}{
// "apiVersion": "apps/v1",
// "kind": "Deployment",
// },
// }
// client.Get(context.Background(), key, deployment)
// resourceIdentifiers := []wait.ResourceIdentifier{deployment}
//
// resolver := wait.NewResolver(client)
// results := resolver.FetchAndResolve(context.Background(), resourceIdentifiers)
//
// WaitForStatus also looks up status for a list of resources, but it will
// block until all the provided resources has reached the Current status or
// the wait is cancelled through the passed-in context. The function returns
// a channel that will provide updates as the status of the different
// resources change.
//
// import (
// "sigs.k8s.io/kustomize/kstatus/wait"
// )
//
// resolver := wait.NewResolver(client)
// eventsChan := resolver.WaitForStatus(context.Background(), resourceIdentifiers, 2 * time.Second)
// for {
// select {
// case event, ok := <-eventsChan:
// if !ok {
// return
// }
// fmt.Printf(event) // do something useful here.
// }
// }
package wait

View File

@@ -1,32 +0,0 @@
package wait
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)
func resourceIdentifierFromObject(object KubernetesObject) ResourceIdentifier {
return ResourceIdentifier{
Name: object.GetName(),
Namespace: object.GetNamespace(),
GroupKind: object.GroupVersionKind().GroupKind(),
}
}
func resourceIdentifiersFromObjects(objects []KubernetesObject) []ResourceIdentifier {
var resourceIdentifiers []ResourceIdentifier
for _, object := range objects {
resourceIdentifiers = append(resourceIdentifiers, resourceIdentifierFromObject(object))
}
return resourceIdentifiers
}
func resourceIdentifierFromRuntimeObject(object runtime.Object) ResourceIdentifier {
gvk := object.GetObjectKind().GroupVersionKind()
r := object.(metav1.Object)
return ResourceIdentifier{
GroupKind: gvk.GroupKind(),
Name: r.GetName(),
Namespace: r.GetNamespace(),
}
}

View File

@@ -1,332 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package wait
import (
"context"
"fmt"
"time"
"github.com/pkg/errors"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/kustomize/kstatus/status"
)
const (
defaultNamespace = "default"
)
// ResourceIdentifier defines the functions needed to identify
// a resource in a cluster. This interface is implemented by
// both unstructured.Unstructured and the standard Kubernetes types.
type KubernetesObject interface {
GetName() string
GetNamespace() string
GroupVersionKind() schema.GroupVersionKind
}
// ResourceIdentifier contains the information needed to uniquely
// identify a resource in a cluster.
type ResourceIdentifier struct {
Name string
Namespace string
GroupKind schema.GroupKind
}
// Equals compares two ResourceIdentifiers and returns true if they
// refer to the same resource. Special handling is needed for namespace
// since an empty namespace for a namespace-scoped resource is defaulted
// to the "default" namespace.
func (r ResourceIdentifier) Equals(other ResourceIdentifier) bool {
isSameNamespace := r.Namespace == other.Namespace ||
(r.Namespace == "" && other.Namespace == defaultNamespace) ||
(r.Namespace == defaultNamespace && other.Namespace == "")
return r.GroupKind == other.GroupKind &&
r.Name == other.Name &&
isSameNamespace
}
// Resolver provides the functions for resolving status of a list of resources.
type Resolver struct {
// client is the client used to talk
// with the cluster. It uses the Reader interface
// from controller-runtime.
client client.Reader
// mapper is the RESTMapper needed to look up mappings
// for resource types.
mapper meta.RESTMapper
// statusComputeFunc defines which function should be used for computing
// the status of a resource. This is available for testing purposes.
statusComputeFunc func(u *unstructured.Unstructured) (*status.Result, error)
// pollInterval defines the frequency with which the resolver should poll
// the cluster for the state of resources. More frequent polling will
// lead to more load on the cluster.
pollInterval time.Duration
}
// NewResolver creates a new resolver with the provided client. Fetching
// and polling of resources will be done using the provided client.
func NewResolver(client client.Reader, mapper meta.RESTMapper, pollInterval time.Duration) *Resolver {
return &Resolver{
client: client,
mapper: mapper,
statusComputeFunc: status.Compute,
pollInterval: pollInterval,
}
}
// ResourceResult is the status result for a given resource. It provides
// information about the resource if the request was successful and an
// error if something went wrong.
type ResourceResult struct {
Result *status.Result
ResourceIdentifier ResourceIdentifier
Error error
}
// FetchAndResolveObjects returns the status for a list of kubernetes objects. These can be provided
// either as Unstructured resources or the specific resource types. It will return the status for each
// of them individually. The provided resources will only be used to get the information needed to
// fetch the updated state of the resources from the cluster.
func (r *Resolver) FetchAndResolveObjects(ctx context.Context, objects []KubernetesObject) []ResourceResult {
resourceIds := resourceIdentifiersFromObjects(objects)
return r.FetchAndResolve(ctx, resourceIds)
}
// FetchAndResolve returns the status for a list of ResourceIdentifiers. It will return
// the status for each of them individually.
func (r *Resolver) FetchAndResolve(ctx context.Context, resourceIDs []ResourceIdentifier) []ResourceResult {
var results []ResourceResult
for _, resourceID := range resourceIDs {
u, err := r.fetchResource(ctx, resourceID)
if err != nil {
if k8serrors.IsNotFound(errors.Cause(err)) {
results = append(results, ResourceResult{
ResourceIdentifier: resourceID,
Result: &status.Result{
Status: status.CurrentStatus,
Message: "Resource does not exist",
},
})
} else {
results = append(results, ResourceResult{
Result: &status.Result{
Status: status.UnknownStatus,
Message: fmt.Sprintf("Error fetching resource from cluster: %v", err),
},
ResourceIdentifier: resourceID,
Error: err,
})
}
continue
}
res, err := r.statusComputeFunc(u)
results = append(results, ResourceResult{
Result: res,
ResourceIdentifier: resourceID,
Error: err,
})
}
return results
}
// Event is returned through the channel returned after a call
// to WaitForStatus. It contains an update to either an individual
// resource or to the aggregate status for the set of resources.
type Event struct {
// Type defines which type of event this is.
Type EventType
// AggregateStatus is the aggregated status for all the provided resources.
AggregateStatus status.Status
// EventResource is information about the event to which this event pertains.
// This is only populated for ResourceUpdate events.
EventResource *EventResource
}
type EventType string
const (
// The status/message for a resource has changed. This also means the
// aggregate status might have changed.
ResourceUpdate EventType = "ResourceUpdate"
// All resources have reached the current status.
Completed EventType = "Completed"
// The wait was stopped before all resources could reach the
// Current status.
Aborted EventType = "Aborted"
)
// EventResource contains information about the resource for which
// a specific Event pertains.
type EventResource struct {
// Identifier contains information that identifies which resource
// this information is about.
ResourceIdentifier ResourceIdentifier
// Status is the latest status for the given resource.
Status status.Status
// Message is more details about the status.
Message string
// Error is set if there was a problem identifying the status
// of the resource. For example, if polling the cluster for information
// about the resource failed.
Error error
}
// WaitForStatus polls all the provided resources until all of them have reached the Current
// status or the timeout specified through the context is reached. Updates on the status
// of individual resources and the aggregate status is provided through the Event channel.
func (r *Resolver) WaitForStatusOfObjects(ctx context.Context, objects []KubernetesObject) <-chan Event {
resourceIds := resourceIdentifiersFromObjects(objects)
return r.WaitForStatus(ctx, resourceIds)
}
// WaitForStatus polls all the resources references by the provided ResourceIdentifiers until
// all of them have reached the Current status or the timeout specified through the context is
// reached. Updates on the status of individual resources and the aggregate status is provided
// through the Event channel.
func (r *Resolver) WaitForStatus(ctx context.Context, resources []ResourceIdentifier) <-chan Event {
eventChan := make(chan Event)
go func() {
ticker := time.NewTicker(r.pollInterval)
defer func() {
ticker.Stop()
// Make sure the channel is closed so consumers can detect that
// we have completed.
close(eventChan)
}()
// No need to wait if we have no resources. We consider
// this a situation where the status is Current.
if len(resources) == 0 {
eventChan <- Event{
Type: Completed,
AggregateStatus: status.CurrentStatus,
EventResource: nil,
}
return
}
// Initiate a new waitStatus object to keep track of the
// resources while polling the state.
waitState := newWaitState(resources, r.statusComputeFunc)
// Check all resources immediately. If the aggregate status is already
// Current, we can exit immediately.
if r.checkAllResources(ctx, waitState, eventChan) {
return
}
// Loop until either all resources have reached the Current status
// or until the wait is cancelled through the context. In both cases
// we will break out of the loop by returning from the function.
for {
select {
case <-ctx.Done():
// The context has been cancelled, so report the most recent
// aggregate status, report it through the channel and then
// break out of the loop (which will close the channel).
eventChan <- Event{
Type: Aborted,
AggregateStatus: waitState.AggregateStatus(),
}
return
case <-ticker.C:
// Every time the ticker fires, we check the status of all
// resources. If the aggregate status has reached Current, checkAllResources
// will return true. If so, we just return.
if r.checkAllResources(ctx, waitState, eventChan) {
return
}
}
}
}()
return eventChan
}
// checkAllResources fetches all resources from the cluster,
// checks if their status has changed and send an event for each resource
// with a new status. In each event, we also include the latest aggregate
// status. Finally, if the aggregate status becomes Current, send a final
// Completed type event. If the aggregate status has become Current, this function
// will return true to signal that it is done.
func (r *Resolver) checkAllResources(ctx context.Context, waitState *waitState, eventChan chan Event) bool {
for resourceID := range waitState.ResourceWaitStates {
// Make sure we have a local copy since we are passing
// pointers to this variable as parameters to functions
u, err := r.fetchResource(ctx, resourceID)
eventResource, updateObserved := waitState.ResourceObserved(resourceID, u, err)
// Find the aggregate status based on the new state for this resource.
aggStatus := waitState.AggregateStatus()
// We want events for changes in status for each resource, so send
// an event for this resource before checking if the aggregate status
// has become Current.
if updateObserved {
eventChan <- Event{
Type: ResourceUpdate,
AggregateStatus: aggStatus,
EventResource: &eventResource,
}
}
// If aggregate status is Current, we are done!
if aggStatus == status.CurrentStatus {
eventChan <- Event{
Type: Completed,
AggregateStatus: status.CurrentStatus,
}
return true
}
}
return false
}
// fetchResource gets the resource given by the identifier from the cluster
// through the client available in the Resolver. It returns the resource
// as an Unstructured.
func (r *Resolver) fetchResource(ctx context.Context, identifier ResourceIdentifier) (*unstructured.Unstructured, error) {
// We need to look up the preferred version for the GroupKind and
// whether the resource type is cluster scoped. We look this
// up with the RESTMapper.
mapping, err := r.mapper.RESTMapping(identifier.GroupKind)
if err != nil {
return nil, err
}
// Resources might not have the namespace set, which means we need to set
// it to `default` if the resource is namespace scoped.
namespace := identifier.Namespace
if namespace == "" && mapping.Scope.Name() == meta.RESTScopeNameNamespace {
namespace = defaultNamespace
}
key := types.NamespacedName{Name: identifier.Name, Namespace: namespace}
u := &unstructured.Unstructured{}
u.SetGroupVersionKind(mapping.GroupVersionKind)
err = r.client.Get(ctx, key, u)
if err != nil {
return nil, errors.Wrap(err, "error fetching resource from cluster")
}
return u, nil
}

View File

@@ -1,594 +0,0 @@
package wait
import (
"context"
"fmt"
"reflect"
"testing"
"time"
"github.com/pkg/errors"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/kubernetes/scheme"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/kustomize/kstatus/status"
)
const (
testTimeout = 1 * time.Minute
testPollInterval = 1 * time.Second
)
func TestFetchAndResolve(t *testing.T) {
type result struct {
status status.Status
error bool
}
testCases := map[string]struct {
resources []runtime.Object
mapperGVKs []schema.GroupVersionKind
expectedResults []result
}{
"no resources": {
resources: []runtime.Object{},
expectedResults: []result{},
},
"single resource": {
resources: []runtime.Object{
&appsv1.Deployment{
TypeMeta: metav1.TypeMeta{
APIVersion: "apps/v1",
Kind: "Deployment",
},
ObjectMeta: metav1.ObjectMeta{
Generation: 1,
Name: "myDeployment",
Namespace: "default",
},
},
},
mapperGVKs: []schema.GroupVersionKind{
appsv1.SchemeGroupVersion.WithKind("Deployment"),
},
expectedResults: []result{
{
status: status.InProgressStatus,
error: false,
},
},
},
"multiple resources": {
resources: []runtime.Object{
&appsv1.StatefulSet{
TypeMeta: metav1.TypeMeta{
APIVersion: "apps/v1",
Kind: "StatefulSet",
},
ObjectMeta: metav1.ObjectMeta{
Generation: 1,
Name: "myStatefulSet",
Namespace: "default",
},
Spec: appsv1.StatefulSetSpec{
UpdateStrategy: appsv1.StatefulSetUpdateStrategy{
Type: appsv1.OnDeleteStatefulSetStrategyType,
},
},
Status: appsv1.StatefulSetStatus{
ObservedGeneration: 1,
},
},
&corev1.Secret{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "Secret",
},
ObjectMeta: metav1.ObjectMeta{
Generation: 1,
Name: "mySecret",
Namespace: "default",
},
},
},
mapperGVKs: []schema.GroupVersionKind{
appsv1.SchemeGroupVersion.WithKind("StatefulSet"),
corev1.SchemeGroupVersion.WithKind("Secret"),
},
expectedResults: []result{
{
status: status.CurrentStatus,
error: false,
},
{
status: status.CurrentStatus,
error: false,
},
},
},
}
for tn, tc := range testCases {
tc := tc
t.Run(tn, func(t *testing.T) {
fakeClient := fake.NewFakeClientWithScheme(scheme.Scheme, tc.resources...)
resolver := NewResolver(fakeClient, newRESTMapper(tc.mapperGVKs...), testPollInterval)
resolver.statusComputeFunc = status.Compute
var identifiers []ResourceIdentifier
for _, resource := range tc.resources {
gvk := resource.GetObjectKind().GroupVersionKind()
r := resource.(metav1.Object)
identifiers = append(identifiers, ResourceIdentifier{
Name: r.GetName(),
Namespace: r.GetNamespace(),
GroupKind: gvk.GroupKind(),
})
}
results := resolver.FetchAndResolve(context.TODO(), identifiers)
for i, res := range results {
id := identifiers[i]
expectedRes := tc.expectedResults[i]
rid := fmt.Sprintf("%s/%s", id.Namespace, id.Name)
if expectedRes.error {
if res.Error == nil {
t.Errorf("expected error for resource %s, but didn't get one", rid)
}
continue
}
if res.Error != nil {
t.Errorf("didn't expected error for resource %s, but got %v", rid, res.Error)
}
if got, want := res.Result.Status, expectedRes.status; got != want {
t.Errorf("expected status %s for resources %s, but got %s", want, rid, got)
}
}
})
}
}
func TestFetchAndResolveUnknownResource(t *testing.T) {
fakeClient := fake.NewFakeClientWithScheme(scheme.Scheme)
resolver := NewResolver(fakeClient, newRESTMapper(appsv1.SchemeGroupVersion.WithKind("Deployment")), testPollInterval)
results := resolver.FetchAndResolve(context.TODO(), []ResourceIdentifier{
{
GroupKind: schema.GroupKind{
Group: "apps",
Kind: "Deployment",
},
Name: "myDeployment",
Namespace: "default",
},
})
if want, got := 1, len(results); want != got {
t.Errorf("expected %d results, but got %d", want, got)
}
res := results[0]
if want, got := status.CurrentStatus, res.Result.Status; got != want {
t.Errorf("expected status %s, but got %s", want, got)
}
if res.Error != nil {
t.Errorf("expected no error, but got %v", res.Error)
}
}
func TestFetchAndResolveWithFetchError(t *testing.T) {
expectedError := errors.New("failed to fetch resource")
resolver := NewResolver(
&fakeReader{
Err: expectedError,
},
newRESTMapper(appsv1.SchemeGroupVersion.WithKind("Deployment")),
testPollInterval,
)
results := resolver.FetchAndResolve(context.TODO(), []ResourceIdentifier{
{
GroupKind: schema.GroupKind{
Group: "apps",
Kind: "Deployment",
},
Name: "myDeployment",
Namespace: "default",
},
})
if want, got := 1, len(results); want != got {
t.Errorf("expected %d results, but got %d", want, got)
}
res := results[0]
if want, got := status.UnknownStatus, res.Result.Status; got != want {
t.Errorf("expected status %s, but got %s", want, got)
}
if want, got := expectedError, errors.Cause(res.Error); got != want {
t.Errorf("expected error %v, but got %v", want, got)
}
}
func TestFetchAndResolveComputeStatusError(t *testing.T) {
expectedError := errors.New("this is a test")
resource := &appsv1.Deployment{
TypeMeta: metav1.TypeMeta{
APIVersion: "apps/v1",
Kind: "Deployment",
},
ObjectMeta: metav1.ObjectMeta{
Generation: 1,
Name: "myDeployment",
Namespace: "default",
},
}
fakeClient := fake.NewFakeClientWithScheme(scheme.Scheme, resource)
resolver := NewResolver(fakeClient, newRESTMapper(appsv1.SchemeGroupVersion.WithKind("Deployment")), testPollInterval)
resolver.statusComputeFunc = func(u *unstructured.Unstructured) (*status.Result, error) {
return &status.Result{
Status: status.UnknownStatus,
Message: "Got an error",
}, expectedError
}
results := resolver.FetchAndResolve(context.TODO(), []ResourceIdentifier{
{
GroupKind: schema.GroupKind{
Group: resource.GroupVersionKind().Group,
Kind: resource.Kind,
},
Name: resource.GetName(),
Namespace: resource.GetNamespace(),
},
})
if want, got := 1, len(results); want != got {
t.Errorf("expected %d results, but got %d", want, got)
}
res := results[0]
if want, got := expectedError, res.Error; got != want {
t.Errorf("expected error %v, but got %v", want, got)
}
if want, got := status.UnknownStatus, res.Result.Status; got != want {
t.Errorf("expected status %s, but got %s", want, got)
}
}
type fakeReader struct {
Called int
Err error
}
func (f *fakeReader) Get(ctx context.Context, key client.ObjectKey, obj runtime.Object) error {
f.Called += 1
return f.Err
}
func (f *fakeReader) List(ctx context.Context, list runtime.Object, opts ...client.ListOption) error {
return errors.New("list not used")
}
func TestWaitForStatus(t *testing.T) {
testCases := map[string]struct {
resources map[runtime.Object][]*status.Result
expectedResourceStatuses map[runtime.Object][]status.Status
expectedAggregateStatuses []status.Status
}{
"no resources": {
resources: map[runtime.Object][]*status.Result{},
expectedResourceStatuses: map[runtime.Object][]status.Status{},
expectedAggregateStatuses: []status.Status{
status.CurrentStatus,
},
},
"single resource": {
resources: map[runtime.Object][]*status.Result{
deploymentResource: {
{
Status: status.InProgressStatus,
Message: "FirstInProgress",
},
{
Status: status.InProgressStatus,
Message: "SecondInProgress",
},
{
Status: status.CurrentStatus,
Message: "CurrentProgress",
},
},
},
expectedResourceStatuses: map[runtime.Object][]status.Status{
deploymentResource: {
status.InProgressStatus,
status.InProgressStatus,
status.CurrentStatus,
},
},
expectedAggregateStatuses: []status.Status{
status.InProgressStatus,
status.InProgressStatus,
status.CurrentStatus,
status.CurrentStatus,
},
},
"multiple resource": {
resources: map[runtime.Object][]*status.Result{
statefulSetResource: {
{
Status: status.InProgressStatus,
Message: "FirstUnknown",
},
{
Status: status.InProgressStatus,
Message: "SecondInProgress",
},
{
Status: status.CurrentStatus,
Message: "CurrentProgress",
},
},
serviceResource: {
{
Status: status.CurrentStatus,
Message: "CurrentImmediately",
},
},
},
expectedResourceStatuses: map[runtime.Object][]status.Status{
statefulSetResource: {
status.InProgressStatus,
status.InProgressStatus,
status.CurrentStatus,
},
serviceResource: {
status.CurrentStatus,
},
},
expectedAggregateStatuses: []status.Status{
status.UnknownStatus,
status.InProgressStatus,
status.InProgressStatus,
status.CurrentStatus,
status.CurrentStatus,
},
},
}
for tn, tc := range testCases {
tc := tc
t.Run(tn, func(t *testing.T) {
var objs []runtime.Object
statusResults := make(map[ResourceIdentifier][]*status.Result)
var identifiers []ResourceIdentifier
for obj, statuses := range tc.resources {
objs = append(objs, obj)
identifier := resourceIdentifierFromRuntimeObject(obj)
identifiers = append(identifiers, identifier)
statusResults[identifier] = statuses
}
statusComputer := statusComputer{
results: statusResults,
resourceCallCount: make(map[ResourceIdentifier]int),
}
resolver := &Resolver{
client: fake.NewFakeClientWithScheme(scheme.Scheme, objs...),
mapper: newRESTMapper(
appsv1.SchemeGroupVersion.WithKind("Deployment"),
appsv1.SchemeGroupVersion.WithKind("StatefulSet"),
corev1.SchemeGroupVersion.WithKind("Service"),
),
statusComputeFunc: statusComputer.Compute,
pollInterval: testPollInterval,
}
eventChan := resolver.WaitForStatus(context.TODO(), identifiers)
var events []Event
timer := time.NewTimer(testTimeout)
loop:
for {
select {
case event, ok := <-eventChan:
if !ok {
break loop
}
events = append(events, event)
case <-timer.C:
t.Fatalf("timeout waiting for resources to reach current status")
}
}
var aggregateStatuses []status.Status
resourceStatuses := make(map[ResourceIdentifier][]status.Status)
for _, e := range events {
aggregateStatuses = append(aggregateStatuses, e.AggregateStatus)
if e.EventResource != nil {
identifier := e.EventResource.ResourceIdentifier
resourceStatuses[identifier] = append(resourceStatuses[identifier], e.EventResource.Status)
}
}
for resource, expectedStatuses := range tc.expectedResourceStatuses {
identifier := resourceIdentifierFromRuntimeObject(resource)
actualStatuses := resourceStatuses[identifier]
if !reflect.DeepEqual(expectedStatuses, actualStatuses) {
t.Errorf("expected statuses %v for resource %s/%s, but got %v", expectedStatuses, identifier.Namespace, identifier.Name, actualStatuses)
}
}
if !reflect.DeepEqual(tc.expectedAggregateStatuses, aggregateStatuses) {
t.Errorf("expected aggregate statuses %v, but got %v", tc.expectedAggregateStatuses, aggregateStatuses)
}
})
}
}
func TestWaitForStatusDeletedResources(t *testing.T) {
statusComputer := statusComputer{
results: make(map[ResourceIdentifier][]*status.Result),
resourceCallCount: make(map[ResourceIdentifier]int),
}
resolver := &Resolver{
client: fake.NewFakeClientWithScheme(scheme.Scheme),
mapper: newRESTMapper(
appsv1.SchemeGroupVersion.WithKind("Deployment"),
corev1.SchemeGroupVersion.WithKind("Service"),
),
statusComputeFunc: statusComputer.Compute,
pollInterval: testPollInterval,
}
depResourceIdentifier := resourceIdentifierFromRuntimeObject(deploymentResource)
serviceResourceIdentifier := resourceIdentifierFromRuntimeObject(serviceResource)
identifiers := []ResourceIdentifier{
depResourceIdentifier,
serviceResourceIdentifier,
}
eventChan := resolver.WaitForStatus(context.TODO(), identifiers)
var events []Event
timer := time.NewTimer(testTimeout)
loop:
for {
select {
case event, ok := <-eventChan:
if !ok {
break loop
}
events = append(events, event)
case <-timer.C:
t.Fatalf("timeout waiting for resources to reach current status")
}
}
expectedEvents := []struct {
aggregateStatus status.Status
hasResource bool
resourceStatus status.Status
}{
{
aggregateStatus: status.UnknownStatus,
hasResource: true,
resourceStatus: status.CurrentStatus,
},
{
aggregateStatus: status.CurrentStatus,
hasResource: true,
resourceStatus: status.CurrentStatus,
},
{
aggregateStatus: status.CurrentStatus,
hasResource: false,
},
}
if want, got := len(expectedEvents), len(events); got != want {
t.Errorf("expected %d events, but got %d", want, got)
}
for i, e := range events {
ee := expectedEvents[i]
if want, got := ee.aggregateStatus, e.AggregateStatus; got != want {
t.Errorf("expected event %d to be %s, but got %s", i, want, got)
}
if ee.hasResource {
if want, got := ee.resourceStatus, e.EventResource.Status; want != got {
t.Errorf("expected resource event %d to be %s, but got %s", i, want, got)
}
}
}
}
type statusComputer struct {
t *testing.T
results map[ResourceIdentifier][]*status.Result
resourceCallCount map[ResourceIdentifier]int
}
func (s *statusComputer) Compute(u *unstructured.Unstructured) (*status.Result, error) {
identifier := resourceIdentifierFromRuntimeObject(u)
resourceResults, ok := s.results[identifier]
if !ok {
s.t.Fatalf("No results available for resource %s/%s", u.GetNamespace(), u.GetName())
}
callCount := s.resourceCallCount[identifier]
var res *status.Result
if len(resourceResults) <= callCount {
res = resourceResults[len(resourceResults)-1]
} else {
res = resourceResults[callCount]
}
s.resourceCallCount[identifier] = callCount + 1
return res, nil
}
var deploymentResource = &appsv1.Deployment{
TypeMeta: metav1.TypeMeta{
APIVersion: "apps/v1",
Kind: "Deployment",
},
ObjectMeta: metav1.ObjectMeta{
Name: "myDeployment",
Namespace: "default",
},
}
var statefulSetResource = &appsv1.StatefulSet{
TypeMeta: metav1.TypeMeta{
APIVersion: "apps/v1",
Kind: "StatefulSet",
},
ObjectMeta: metav1.ObjectMeta{
Name: "myStatefulSet",
Namespace: "default",
},
}
var serviceResource = &corev1.Service{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "Service",
},
ObjectMeta: metav1.ObjectMeta{
Name: "myService",
Namespace: "default",
},
}
func newRESTMapper(gvks ...schema.GroupVersionKind) meta.RESTMapper {
var groupVersions []schema.GroupVersion
for _, gvk := range gvks {
groupVersions = append(groupVersions, gvk.GroupVersion())
}
mapper := meta.NewDefaultRESTMapper(groupVersions)
for _, gvk := range gvks {
mapper.Add(gvk, meta.RESTScopeNamespace)
}
return mapper
}

View File

@@ -1,169 +0,0 @@
package wait
import (
"fmt"
"reflect"
"github.com/pkg/errors"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"sigs.k8s.io/kustomize/kstatus/status"
)
// waitState keeps the state about the resources and their last
// observed state. This is used to determine any changes in state
// so events can be sent when needed.
type waitState struct {
// ResourceWaitStates contains wait state for each of the resources.
ResourceWaitStates map[ResourceIdentifier]*resourceWaitState
// statusComputeFunc defines the function used to compute the state of
// a single resource. This is available for testing purposes.
statusComputeFunc func(u *unstructured.Unstructured) (*status.Result, error)
}
// resourceWaitState contains state information about an individual resource.
type resourceWaitState struct {
FirstSeenGeneration *int64
HasBeenCurrent bool
Observed bool
LastEvent *EventResource
}
// newWaitState creates a new waitState object and initializes it with the
// provided slice of resources and the provided statusComputeFunc.
func newWaitState(resourceIDs []ResourceIdentifier, statusComputeFunc func(u *unstructured.Unstructured) (*status.Result, error)) *waitState {
resourceWaitStates := make(map[ResourceIdentifier]*resourceWaitState)
for _, resourceID := range resourceIDs {
resourceWaitStates[resourceID] = &resourceWaitState{}
}
return &waitState{
ResourceWaitStates: resourceWaitStates,
statusComputeFunc: statusComputeFunc,
}
}
// AggregateStatus computes the aggregate status for all the resources.
// TODO: Ideally we would like this to be pluggable for different strategies.
func (w *waitState) AggregateStatus() status.Status {
allCurrent := true
for _, rws := range w.ResourceWaitStates {
if !rws.Observed {
return status.UnknownStatus
}
if !rws.HasBeenCurrent {
allCurrent = false
}
}
if allCurrent {
return status.CurrentStatus
}
return status.InProgressStatus
}
// ResourceObserved notifies the waitState that we have new state for
// a resource. This also accepts an error in case fetching the resource
// from a cluster failed. It returns an EventResource object that contains
// information about the observed resource, including the identifier and
// the latest status for the resource. The function also returns a bool value
// that will be true if the status of the observed resource has changed
// since the previous observation and false it not. This is used to determine
// whether a new event should be sent based on this observation.
func (w *waitState) ResourceObserved(resourceID ResourceIdentifier, resource *unstructured.Unstructured, err error) (EventResource, bool) {
// Check for nil is not needed here as the id passed in comes
// from iterating over the keys of the map.
rws := w.ResourceWaitStates[resourceID]
eventResource := w.getEventResource(resourceID, resource, err)
// If the new eventResource is identical to the previous one, we return
// with the last return value indicating this is not a new event.
if rws.LastEvent != nil && reflect.DeepEqual(eventResource, *rws.LastEvent) {
return eventResource, false
}
rws.LastEvent = &eventResource
return eventResource, true
}
// getEventResource creates a new EventResource for the resource identified by
// the provided resourceKey. The EventResource contains information about the
// latest status for the given resource, so it computes status for the resource
// as well as check for deletion.
func (w *waitState) getEventResource(resourceID ResourceIdentifier, resource *unstructured.Unstructured, err error) EventResource {
// Get the resourceWaitState for this resource. It contains information
// of the previous observed statuses. We don't need to check for nil here
// as the identifier comes from iterating over the keys of the
// ResourceWaitState map.
r := w.ResourceWaitStates[resourceID]
// If fetching the resource from the cluster failed, we don't really
// know anything about the status of the resource, so simply
// report the status as Unknown.
if err != nil && !k8serrors.IsNotFound(errors.Cause(err)) {
return EventResource{
ResourceIdentifier: resourceID,
Status: status.UnknownStatus,
Message: fmt.Sprintf("Error: %s", err),
Error: err,
}
}
// If we get here, we have successfully fetched the resource from
// the cluster, or discovered that it doesn't exist.
r.Observed = true
// We treat a non-existent resource as Current. This is to properly
// handle deletion scenarios.
if k8serrors.IsNotFound(errors.Cause(err)) {
r.HasBeenCurrent = true
return EventResource{
ResourceIdentifier: resourceID,
Status: status.CurrentStatus,
Message: fmt.Sprintf("Resource has been deleted"),
}
}
// We want to capture the first seen generation of the resource. This
// allows us to discover if a resource is updated while we are waiting
// for it to become Current.
if r.FirstSeenGeneration != nil {
gen := resource.GetGeneration()
r.FirstSeenGeneration = &gen
}
if resource.GetDeletionTimestamp() != nil {
return EventResource{
ResourceIdentifier: resourceID,
Status: status.TerminatingStatus,
Message: fmt.Sprintf("Resource is terminating"),
}
}
statusResult, err := w.statusComputeFunc(resource)
// If we can't compute status for the resource, we report the status
// as Unknown.
if err != nil {
return EventResource{
ResourceIdentifier: resourceID,
Status: status.UnknownStatus,
Message: fmt.Sprintf("Error: %s", err),
Error: err,
}
}
// We record whether a resource has ever been Current. This makes
// sure we can report a set of resources as being Current if all
// of them has reached the Current status at some point, but not
// necessarily at the same time.
if statusResult.Status == status.CurrentStatus {
r.HasBeenCurrent = true
}
return EventResource{
ResourceIdentifier: resourceID,
Status: statusResult.Status,
Message: statusResult.Message,
}
}

View File

@@ -14,7 +14,6 @@ require (
sigs.k8s.io/controller-runtime v0.4.0
sigs.k8s.io/kustomize/api v0.4.1
sigs.k8s.io/kustomize/cmd/config v0.2.0
sigs.k8s.io/kustomize/kstatus v0.0.2
sigs.k8s.io/kustomize/kyaml v0.3.2
sigs.k8s.io/yaml v1.2.0
)

View File

@@ -202,6 +202,7 @@ github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJA
github.com/gofrs/flock v0.0.0-20190320160742-5135e617513b/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
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 h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I=
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
@@ -240,6 +241,7 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@@ -605,6 +607,7 @@ golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -753,12 +756,14 @@ sigs.k8s.io/cli-utils v0.12.0 h1:+CvYwQAEtKvcx/NaUVF9rDKvY91VwJj+i7D2lWBMYc0=
sigs.k8s.io/cli-utils v0.12.0/go.mod h1:H35YA5iJIM7EVNgqDTjX2dgt4wE23zmnXOTSTlyD+PE=
sigs.k8s.io/controller-runtime v0.4.0 h1:wATM6/m+3w8lj8FXNaO6Fs/rq/vqoOjO1Q116Z9NPsg=
sigs.k8s.io/controller-runtime v0.4.0/go.mod h1:ApC79lpY3PHW9xj/w9pj+lYkLgwAAUZwfXkME1Lajns=
sigs.k8s.io/kustomize v1.0.11 h1:Yb+6DDt9+aR2AvQApvUaKS/ugteeG4MPyoFeUHiPOjk=
sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0=
sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU=
sigs.k8s.io/kustomize/cmd/config v0.2.0 h1:VNAWKb1JLl7dFjMAD5MwdZpGjreN3qL63C8DKtsUYck=
sigs.k8s.io/kustomize/cmd/config v0.2.0/go.mod h1:oXzY7QJS6JlmWgusEjra2O3cW7GSIICXa59/3DvjBfE=
sigs.k8s.io/kustomize/kstatus v0.0.2 h1:7GoHi/Vq7rIAS8AQONlfcdaCpVXY0HqzNhU5us7dToA=
sigs.k8s.io/kustomize/kstatus v0.0.2/go.mod h1:6qUKWLy4+yGExtjbs+fibz2tOBZG7413yx2NHyAzIU0=
sigs.k8s.io/kustomize/kustomize v0.0.0-20191024000301-ce7ebe3299dd h1:naYNcVnc1wjElh8I9o/NP2oqSlYCJJERusQrC3WGcBQ=
sigs.k8s.io/kustomize/kyaml v0.1.4/go.mod h1:461i94nj0h0ylJ6w83jLkR4SqqVhn1iY6fjD0JSTQeE=
sigs.k8s.io/kustomize/kyaml v0.2.0 h1:xlFMTAHTSIFvHzih5nSFQNxM+3ksnt5Nhfv5dLQWDA8=
sigs.k8s.io/kustomize/kyaml v0.2.0/go.mod h1:72/rLkSi+L/pHM1oCjwrf3ClU+tH5kZQvvdLSqIHwWU=

View File

@@ -19,7 +19,6 @@ import (
"sigs.k8s.io/kustomize/kustomize/v3/internal/commands/build"
"sigs.k8s.io/kustomize/kustomize/v3/internal/commands/create"
"sigs.k8s.io/kustomize/kustomize/v3/internal/commands/edit"
"sigs.k8s.io/kustomize/kustomize/v3/internal/commands/status"
"sigs.k8s.io/kustomize/kustomize/v3/internal/commands/version"
)
@@ -44,7 +43,6 @@ See https://sigs.k8s.io/kustomize
edit.NewCmdEdit(fSys, v, uf),
create.NewCmdCreate(fSys, uf),
version.NewCmdVersion(stdOut),
status.NewCmdStatus(),
)
configcobra.AddCommands(c, "kustomize")

View File

@@ -1,31 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
//go:generate $GOBIN/mdtogo docs/commands generateddocs/commands --license=none
package status
import (
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/kustomize/v3/internal/commands/status/cmd"
"sigs.k8s.io/kustomize/kyaml/commandutil"
)
func NewCmdStatus() *cobra.Command {
var c = &cobra.Command{
Use: "status",
Short: "[Alpha] Commands for working with resource status.",
Hidden: commandutil.GetAlphaEnabled(),
}
if !commandutil.GetAlphaEnabled() {
c.Short = "[Alpha] To enable set KUSTOMIZE_ENABLE_ALPHA_COMMANDS=true"
c.Long = "[Alpha] To enable set KUSTOMIZE_ENABLE_ALPHA_COMMANDS=true"
return c
}
c.AddCommand(cmd.FetchCommand())
c.AddCommand(cmd.WaitCommand())
c.AddCommand(cmd.EventsCommand())
return c
}

View File

@@ -1,104 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package cmd
import (
"context"
"time"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/kustomize/v3/internal/commands/status/generateddocs/commands"
"sigs.k8s.io/kustomize/kyaml/kio"
)
// GetEventsRunner returns a command EventsRunner.
func GetEventsRunner() *EventsRunner {
r := &EventsRunner{
newResolverFunc: newResolver,
}
c := &cobra.Command{
Use: "events DIR...",
Short: commands.EventsShort,
Long: commands.EventsLong,
Example: commands.EventsExamples,
RunE: r.runE,
}
c.Flags().BoolVar(&r.IncludeSubpackages, "include-subpackages", true,
"also print resources from subpackages.")
c.Flags().DurationVar(&r.Interval, "interval", 2*time.Second,
"check every n seconds.")
c.Flags().DurationVar(&r.Timeout, "timeout", 60*time.Second,
"give up after n seconds.")
r.Command = c
return r
}
func EventsCommand() *cobra.Command {
return GetEventsRunner().Command
}
// EventsRunner captures the parameters for the command
// and contains the run function.
type EventsRunner struct {
IncludeSubpackages bool
Interval time.Duration
Timeout time.Duration
Command *cobra.Command
newResolverFunc newResolverFunc
}
func (r *EventsRunner) runE(c *cobra.Command, args []string) error {
ctx := context.Background()
resolver, mapper, err := r.newResolverFunc(r.Interval)
if err != nil {
return errors.Wrap(err, "error creating resolver")
}
// Set up a CaptureIdentifierFilter and run all inputs through the
// filter with the pipeline to capture the inventory of resources
// which we are interested in.
captureFilter := &CaptureIdentifiersFilter{
Mapper: mapper,
}
filters := []kio.Filter{captureFilter}
var inputs []kio.Reader
for _, a := range args {
inputs = append(inputs, kio.LocalPackageReader{
PackagePath: a,
IncludeSubpackages: r.IncludeSubpackages,
})
}
if len(inputs) == 0 {
inputs = append(inputs, &kio.ByteReader{Reader: c.InOrStdin()})
}
err = kio.Pipeline{
Inputs: inputs,
Filters: filters,
}.Execute()
if err != nil {
return errors.Wrap(err, "error reading manifests")
}
// Create a new printer that knows how to print updates about
// resourdes and their aggregate status in the events format.
printer := newEventPrinter(c.OutOrStdout(), c.OutOrStderr())
ctx, cancel := context.WithTimeout(ctx, r.Timeout)
defer cancel()
resChannel := resolver.WaitForStatus(ctx, captureFilter.Identifiers)
// Print events until the channel is closed. This will happen
// either because all resources has reached the Current status
// or it has timed out.
for msg := range resChannel {
printer.printEvent(msg)
}
return nil
}

View File

@@ -1,317 +0,0 @@
package cmd
import (
"bytes"
"fmt"
"reflect"
"regexp"
"strings"
"testing"
"github.com/stretchr/testify/assert"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/kustomize/kstatus/status"
"sigs.k8s.io/kustomize/kstatus/wait"
)
func TestEventsNoResources(t *testing.T) {
inBuffer := &bytes.Buffer{}
outBuffer := &bytes.Buffer{}
fakeClient := &FakeClient{}
r := GetEventsRunner()
r.newResolverFunc = fakeResolver(fakeClient)
r.Command.SetArgs([]string{})
r.Command.SetIn(inBuffer)
r.Command.SetOut(outBuffer)
err := r.Command.Execute()
if !assert.NoError(t, err) {
return
}
eventOutput := parseEventOutput(t, outBuffer.String())
if want, got := 1, len(eventOutput.events); want != got {
t.Errorf("expected %d events, but got %d", want, got)
}
event := eventOutput.events[0]
if want, got := status.CurrentStatus, event.aggStatus; want != got {
t.Errorf("expected agg status %s, but got %s", want, got)
}
}
func TestEventsMultipleUpdates(t *testing.T) {
inBuffer := &bytes.Buffer{}
outBuffer := &bytes.Buffer{}
_, err := fmt.Fprint(inBuffer, `
apiVersion: apps/v1
kind: Deployment
metadata:
name: bar
namespace: default
`)
if !assert.NoError(t, err) {
return
}
fakeClient := &FakeClient{
resourceCallbackMap: map[string]ResourceGetCallback{
"Deployment": createDeploymentStatusFunc(),
},
}
r := GetEventsRunner()
r.newResolverFunc = fakeResolver(fakeClient, appsv1.SchemeGroupVersion.WithKind("Deployment"))
r.Command.SetArgs([]string{})
r.Command.SetIn(inBuffer)
r.Command.SetOut(outBuffer)
err = r.Command.Execute()
if !assert.NoError(t, err) {
return
}
eventOutput := parseEventOutput(t, outBuffer.String())
aggStatuses := eventOutput.allAggStatuses()
expectedAggStatuses := []status.Status{
status.InProgressStatus,
status.InProgressStatus,
status.InProgressStatus,
status.InProgressStatus,
status.CurrentStatus,
status.CurrentStatus,
}
if !reflect.DeepEqual(aggStatuses, expectedAggStatuses) {
t.Errorf("expected agg statuses to be %s, but got %s", joinStatuses(expectedAggStatuses),
joinStatuses(aggStatuses))
}
resources := eventOutput.allResources()
if want, got := 1, len(resources); want != got {
t.Errorf("expected %d resource, but got %d", want, got)
}
resource := resources[0]
resourceStatuses := eventOutput.statusesForResource(resource)
expectedResourceStatuses := []status.Status{
status.InProgressStatus,
status.InProgressStatus,
status.InProgressStatus,
status.InProgressStatus,
status.CurrentStatus,
}
if !reflect.DeepEqual(resourceStatuses, expectedResourceStatuses) {
t.Errorf("expected statuses to be %s, but got %s", joinStatuses(expectedResourceStatuses),
joinStatuses(resourceStatuses))
}
}
func TestEventsMultipleResources(t *testing.T) {
inBuffer := &bytes.Buffer{}
outBuffer := &bytes.Buffer{}
_, err := fmt.Fprint(inBuffer, `
apiVersion: v1
kind: List
items:
- apiVersion: v1
kind: Pod
metadata:
name: foo
namespace: default
- apiVersion: v1
kind: Service
metadata:
name: bar
namespace: default
`)
if !assert.NoError(t, err) {
return
}
fakeClient := &FakeClient{
resourceCallbackMap: map[string]ResourceGetCallback{
"Pod": createPodStatusFunc(),
"Service": createServiceStatusFunc(),
},
}
r := GetEventsRunner()
r.newResolverFunc = fakeResolver(fakeClient, corev1.SchemeGroupVersion.WithKind("Pod"),
corev1.SchemeGroupVersion.WithKind("Service"))
r.Command.SetArgs([]string{})
r.Command.SetIn(inBuffer)
r.Command.SetOut(outBuffer)
err = r.Command.Execute()
if !assert.NoError(t, err) {
return
}
eventOutput := parseEventOutput(t, outBuffer.String())
aggStatuses := eventOutput.allAggStatuses()
expectedAggStatuses := []status.Status{
status.UnknownStatus,
status.CurrentStatus,
status.CurrentStatus,
}
if !reflect.DeepEqual(aggStatuses, expectedAggStatuses) {
t.Errorf("expected agg statuses to be %s, but got %s", joinStatuses(expectedAggStatuses),
joinStatuses(aggStatuses))
}
resources := eventOutput.allResources()
if want, got := 2, len(resources); got != want {
t.Errorf("expected %d resource, but got %d", want, got)
}
for _, resource := range resources {
resourceStatuses := eventOutput.statusesForResource(resource)
if want, got := status.CurrentStatus, resourceStatuses[len(resourceStatuses)-1]; want != got {
t.Errorf("expected resource %q to have final status %s, but got %s", resource.name, want, got)
}
}
}
type EventOutput struct {
events []EventOutputLine
unknownLines []string
}
func (e *EventOutput) allAggStatuses() []status.Status {
var aggStatuses []status.Status
for _, event := range e.events {
aggStatuses = append(aggStatuses, event.aggStatus)
}
return aggStatuses
}
func (e *EventOutput) allResources() []ResourceIdentifier {
var resources []ResourceIdentifier
seenResources := make(map[ResourceIdentifier]bool)
for _, event := range e.events {
if !event.isResourceUpdateEvent() {
continue
}
r := ResourceIdentifier{
namespace: event.namespace,
name: event.name,
kind: event.kind,
}
if _, found := seenResources[r]; !found {
resources = append(resources, r)
seenResources[r] = true
}
}
return resources
}
func (e *EventOutput) statusesForResource(resource ResourceIdentifier) []status.Status {
var statuses []status.Status
for _, event := range e.events {
if !event.isResourceUpdateEvent() {
continue
}
identifier := ResourceIdentifier{
namespace: event.namespace,
name: event.name,
kind: event.kind,
}
if identifier.Equals(resource) {
statuses = append(statuses, event.status)
}
}
return statuses
}
type EventOutputLine struct {
eventType wait.EventType
aggStatus status.Status
kind string
namespace string
name string
status status.Status
message string
}
func (e *EventOutputLine) isResourceUpdateEvent() bool {
return e.eventType == wait.ResourceUpdate
}
var (
eventRegex = regexp.MustCompile(`^\s*` +
`(?P<aggStatus>(?:Current|InProgress|Failed|Terminating|Unknown))\s+` +
`(?P<message>.*\S)` +
`\s*$`)
resourceEventRegex = regexp.MustCompile(`^\s*` +
`(?P<namespace>\S+)\s+` +
`(?P<aggStatus>(?:Current|InProgress|Failed|Terminating|Unknown))\s+` +
`(?P<resourceType>\S+)\s+` +
`(?P<name>\S+)\s+` +
`(?P<status>(?:Current|InProgress|Failed|Terminating|Unknown))\s+` +
`(?P<message>.*\S)` +
`\s*$`)
eventHeaderRegex = regexp.MustCompile(`^\s*` +
`NAMESPACE\s+` +
`AGG STATUS\s+` +
`TYPE\s+` +
`NAME\s+` +
`STATUS\s+` +
`MESSAGE` +
`\s*$`)
)
func parseEventOutput(_ *testing.T, output string) EventOutput {
var eventOutput EventOutput
lines := strings.Split(output, "\n")
for _, line := range lines {
if len(line) == 0 {
continue // Ignore empty lines
}
match := eventHeaderRegex.FindStringSubmatch(line)
if match != nil {
continue // Ignore headers
}
match = eventRegex.FindStringSubmatch(line)
if match != nil {
aggStatus := status.FromStringOrDie(match[1])
var eventType wait.EventType
if aggStatus == status.CurrentStatus {
eventType = wait.Completed
} else {
eventType = wait.Aborted
}
eventOutput.events = append(eventOutput.events, EventOutputLine{
eventType: eventType,
aggStatus: aggStatus,
message: match[2],
})
}
match = resourceEventRegex.FindStringSubmatch(line)
if match == nil {
eventOutput.unknownLines = append(eventOutput.unknownLines, line)
continue
}
eventOutput.events = append(eventOutput.events, EventOutputLine{
eventType: wait.ResourceUpdate,
aggStatus: status.FromStringOrDie(match[2]),
kind: match[3],
namespace: match[1],
name: match[4],
status: status.FromStringOrDie(match[5]),
message: match[6],
})
}
return eventOutput
}

View File

@@ -1,127 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package cmd
import (
"context"
"time"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/kstatus/status"
"sigs.k8s.io/kustomize/kstatus/wait"
"sigs.k8s.io/kustomize/kustomize/v3/internal/commands/status/generateddocs/commands"
"sigs.k8s.io/kustomize/kyaml/kio"
)
// GetFetchRunner returns a command FetchRunner.
func GetFetchRunner() *FetchRunner {
r := &FetchRunner{
newResolverFunc: newResolver,
}
c := &cobra.Command{
Use: "fetch DIR...",
Short: commands.FetchShort,
Long: commands.FetchLong,
Example: commands.FetchExamples,
RunE: r.runE,
}
c.Flags().BoolVar(&r.IncludeSubpackages, "include-subpackages", true,
"also print resources from subpackages.")
r.Command = c
return r
}
func FetchCommand() *cobra.Command {
return GetFetchRunner().Command
}
// FetchRunner captures the parameters for the command and contains
// the run function.
type FetchRunner struct {
IncludeSubpackages bool
Command *cobra.Command
newResolverFunc newResolverFunc
}
func (r *FetchRunner) runE(c *cobra.Command, args []string) error {
ctx := context.Background()
resolver, mapper, err := r.newResolverFunc(time.Minute)
if err != nil {
return errors.Wrap(err, "error creating resolver")
}
// Set up a CaptureIdentifierFilter and run all inputs through the
// filter with the pipeline to capture the inventory of resources
// which we are interested in.
captureFilter := &CaptureIdentifiersFilter{
Mapper: mapper,
}
filters := []kio.Filter{captureFilter}
var inputs []kio.Reader
for _, a := range args {
inputs = append(inputs, kio.LocalPackageReader{
PackagePath: a,
IncludeSubpackages: r.IncludeSubpackages,
})
}
if len(inputs) == 0 {
inputs = append(inputs, &kio.ByteReader{Reader: c.InOrStdin()})
}
err = kio.Pipeline{
Inputs: inputs,
Filters: filters,
}.Execute()
if err != nil {
return errors.Wrap(err, "error reading manifests")
}
// Pass in the inventory of resources to the FetchAndResolve function
// on the resolver. It will return the status (or an error) for each
// resource in the inventory.
results := resolver.FetchAndResolve(ctx, captureFilter.Identifiers)
// Create new printer that knows how to print resource statuses
// in a table format and ask it to print the results.
newTablePrinter(FetchStatusInfo{results}, c.OutOrStdout(), c.OutOrStderr(), false).Print()
return nil
}
// FetchStatusInfo wraps the results from the FetchAndResolve function
// to the format expected in the TablePrinter.
type FetchStatusInfo struct {
Results []wait.ResourceResult
}
// CurrentStatus returns the latest information known about the
// status of each of the resources. For FetchStatusInfo, the result
// is never updated, so it just returns the information provided
// by the slice of wait.ResourceResult at creation.
func (f FetchStatusInfo) CurrentStatus() StatusData {
var resourceData []ResourceStatusData
for _, res := range f.Results {
rsd := ResourceStatusData{
Identifier: res.ResourceIdentifier,
}
if res.Error != nil {
rsd.Status = status.UnknownStatus
rsd.Message = res.Error.Error()
} else {
rsd.Status = res.Result.Status
rsd.Message = res.Result.Message
}
resourceData = append(resourceData, rsd)
}
return StatusData{
AggregateStatus: status.UnknownStatus,
ResourceStatuses: resourceData,
}
}

View File

@@ -1,231 +0,0 @@
package cmd
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
"github.com/acarl005/stripansi"
"github.com/stretchr/testify/assert"
appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/kustomize/kstatus/status"
)
func TestEmptyManifest(t *testing.T) {
inBuffer := &bytes.Buffer{}
outBuffer := &bytes.Buffer{}
fakeClient := fake.NewFakeClientWithScheme(scheme)
r := GetFetchRunner()
r.newResolverFunc = fakeResolver(fakeClient)
r.Command.SetArgs([]string{})
r.Command.SetIn(inBuffer)
r.Command.SetOut(outBuffer)
err := r.Command.Execute()
if !assert.NoError(t, err) {
return
}
output := outBuffer.String()
lines := strings.Split(output, "\n")
if want, got := 2, len(lines); want != got {
t.Errorf("Expected %d lines, but got %d", want, got)
}
}
func TestFetchStatusFromManifestStdIn(t *testing.T) {
inBuffer := &bytes.Buffer{}
outBuffer := &bytes.Buffer{}
_, err := fmt.Fprint(inBuffer, `
apiVersion: apps/v1
kind: Deployment
metadata:
name: bar
namespace: default
`)
if !assert.NoError(t, err) {
return
}
deployment := createDeployment("bar", "default", 42, appsv1.DeploymentStatus{
ObservedGeneration: 1,
})
fakeClient := fake.NewFakeClientWithScheme(scheme, deployment)
r := GetFetchRunner()
r.newResolverFunc = fakeResolver(fakeClient, appsv1.SchemeGroupVersion.WithKind("Deployment"))
r.Command.SetArgs([]string{})
r.Command.SetIn(inBuffer)
r.Command.SetOut(outBuffer)
err = r.Command.Execute()
if !assert.NoError(t, err) {
return
}
cleanOutput := stripansi.Strip(outBuffer.String())
tableOutput := parseTableOutput(t, cleanOutput)
expectedResource := ResourceIdentifier{
apiVersion: "apps",
kind: "Deployment",
namespace: "default",
name: "bar",
}
expectedStatus := status.InProgressStatus
expectedMessage := "Deployment generation is 2, but latest observed generation is 1"
verifyOutputContains(t, tableOutput, expectedResource, expectedStatus, expectedMessage)
}
//nolint:funlen
func TestFetchStatusFromManifestsFiles(t *testing.T) {
d, err := ioutil.TempDir("", "status-fetch-test")
if !assert.NoError(t, err) {
return
}
defer os.RemoveAll(d)
err = ioutil.WriteFile(filepath.Join(d, "dep.yaml"), []byte(`
apiVersion: apps/v1
kind: Deployment
metadata:
name: foo
namespace: default
`), 0600)
if !assert.NoError(t, err) {
return
}
err = ioutil.WriteFile(filepath.Join(d, "svc.yaml"), []byte(`
apiVersion: v1
kind: Service
metadata:
name: foo
namespace: default
`), 0600)
if !assert.NoError(t, err) {
return
}
replicas := int32(42)
deployment := createDeployment("foo", "default", replicas, appsv1.DeploymentStatus{
ObservedGeneration: 2,
Replicas: replicas,
ReadyReplicas: replicas,
AvailableReplicas: replicas,
UpdatedReplicas: replicas,
Conditions: []appsv1.DeploymentCondition{
{
Type: appsv1.DeploymentAvailable,
Status: v1.ConditionTrue,
},
},
})
service := createService("foo", "default")
fakeClient := fake.NewFakeClientWithScheme(scheme, deployment, service)
outBuffer := &bytes.Buffer{}
r := GetFetchRunner()
r.newResolverFunc = fakeResolver(fakeClient, appsv1.SchemeGroupVersion.WithKind("Deployment"),
v1.SchemeGroupVersion.WithKind("Service"))
r.Command.SetArgs([]string{d})
r.Command.SetOut(outBuffer)
err = r.Command.Execute()
if !assert.NoError(t, err) {
return
}
cleanOutput := stripansi.Strip(outBuffer.String())
tableOutput := parseTableOutput(t, cleanOutput)
expectedDeploymentResource := ResourceIdentifier{
apiVersion: "apps",
kind: "Deployment",
namespace: "default",
name: "foo",
}
expectedDeploymentStatus := status.CurrentStatus
expectedDeploymentMessage := "Deployment is available. Replicas: 42"
verifyOutputContains(t, tableOutput, expectedDeploymentResource, expectedDeploymentStatus, expectedDeploymentMessage)
expectedServiceResource := ResourceIdentifier{
apiVersion: "",
kind: "Service",
namespace: "default",
name: "foo",
}
expectedServiceStatus := status.CurrentStatus
expectedServiceMessage := "Service is ready"
verifyOutputContains(t, tableOutput, expectedServiceResource, expectedServiceStatus, expectedServiceMessage)
}
func createDeployment(name, namespace string, replicas int32, status appsv1.DeploymentStatus) *appsv1.Deployment {
return &appsv1.Deployment{
TypeMeta: metav1.TypeMeta{
APIVersion: "apps/v1",
Kind: "Deployment",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Generation: 2,
},
Spec: appsv1.DeploymentSpec{
Replicas: &replicas,
},
Status: status,
}
}
func createService(name, namespace string) *v1.Service {
return &v1.Service{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "Service",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
}
}
func verifyOutputContains(t *testing.T, tableOutput TableOutput, resource ResourceIdentifier, status status.Status, message string) {
if len(tableOutput.Frames) == 0 {
t.Fatalf("expected match for resource %s, but output had no frames", resource.name)
}
firstFrame := tableOutput.Frames[0]
var foundResource ResourceOutput
match := false
for _, resourceOutput := range firstFrame.Resources {
if resourceOutput.identifier.Equals(resource) {
foundResource = resourceOutput
match = true
break
}
}
if !match {
t.Errorf("expected match for resource %s, but didn't find it", resource.name)
}
if want, got := status, foundResource.status; want != got {
t.Errorf("expected status %s for resource %s, but got %s", want, resource.name, got)
}
if want, got := message, foundResource.message; !strings.HasPrefix(want, got) {
t.Errorf("expected message %s for resource %s, but got %s", want, resource.name, got)
}
}

View File

@@ -1,264 +0,0 @@
package cmd
import (
"context"
"fmt"
"regexp"
"strings"
"testing"
"time"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/kustomize/kstatus/status"
"sigs.k8s.io/kustomize/kstatus/wait"
)
type TableOutput struct {
Frames []TableOutputFrame
UnknownRows []string
}
func (t *TableOutput) allAggStatuses() []status.Status {
var statuses []status.Status
for _, frame := range t.Frames {
if frame.AggregateStatus != "" {
statuses = append(statuses, frame.AggregateStatus)
}
}
return statuses
}
func (t *TableOutput) dedupedAggStatuses() []status.Status {
var dedupedStatuses []status.Status
statuses := t.allAggStatuses()
var previousStatus status.Status
for _, s := range statuses {
if s != previousStatus {
dedupedStatuses = append(dedupedStatuses, s)
previousStatus = s
}
}
return dedupedStatuses
}
func (t *TableOutput) resources() []ResourceIdentifier {
seenResources := make(map[ResourceIdentifier]bool)
var resources []ResourceIdentifier
for _, frame := range t.Frames {
for _, resource := range frame.Resources {
r := resource.identifier
_, found := seenResources[r]
if !found {
seenResources[r] = true
resources = append(resources, r)
}
}
}
return resources
}
func (t *TableOutput) dedupedStatusesForResource(resource ResourceIdentifier) []status.Status {
var dedupedStatuses []status.Status
var previousStatus status.Status
for _, frame := range t.Frames {
for _, r := range frame.Resources {
if r.identifier.Equals(resource) {
if r.status != previousStatus {
previousStatus = r.status
dedupedStatuses = append(dedupedStatuses, r.status)
}
}
}
}
return dedupedStatuses
}
type TableOutputFrame struct {
AggregateStatus status.Status
Resources []ResourceOutput
}
type ResourceIdentifier struct {
apiVersion string
kind string
name string
namespace string
}
func (r ResourceIdentifier) Equals(identifier ResourceIdentifier) bool {
return r.apiVersion == identifier.apiVersion &&
r.kind == identifier.kind &&
r.namespace == identifier.namespace &&
r.name == identifier.name
}
type ResourceOutput struct {
identifier ResourceIdentifier
status status.Status
message string
}
var (
headerRegex = regexp.MustCompile(`^\s*TYPE\s+NAMESPACE\s+NAME\s+STATUS\s+MESSAGE\s*$`)
resourceRegex = regexp.MustCompile(`^(?P<resourceType>\S+)\s+(?P<namespace>\S+)\s+(?P<name>\S+)\s+(?P<status>\S+)\s+(?P<message>.*\S)\s*$`)
aggStatusRegex = regexp.MustCompile(`^\s*AggregateStatus: (?P<aggregateStatus>\S+)\s*$`)
)
func parseTableOutput(_ *testing.T, output string) TableOutput {
tableOutput := TableOutput{}
lines := strings.Split(output, "\n")
hasAggStatus := false
var currentFrame TableOutputFrame
for i, line := range lines {
if len(line) == 0 {
continue // We don't care about empty lines.
}
// Check for lines with aggregate status. They are not always present, but if they are,
// they always start a new frame of output.
match := aggStatusRegex.FindStringSubmatch(line)
if match != nil {
hasAggStatus = true
if i != 0 {
tableOutput.Frames = append(tableOutput.Frames, currentFrame)
}
currentFrame = TableOutputFrame{
AggregateStatus: status.FromStringOrDie(match[1]),
}
continue
}
match = headerRegex.FindStringSubmatch(line)
if match != nil {
if !hasAggStatus {
if i != 0 {
tableOutput.Frames = append(tableOutput.Frames, currentFrame)
}
currentFrame = TableOutputFrame{}
}
continue
}
match = resourceRegex.FindStringSubmatch(line)
if match != nil {
var identifier ResourceIdentifier
resourceType := match[1]
parts := strings.Split(resourceType, "/")
if len(parts) == 2 {
identifier.apiVersion = parts[0]
identifier.kind = parts[1]
} else {
identifier.apiVersion = strings.Join(parts[:2], "/")
identifier.kind = parts[2]
}
identifier.namespace = match[2]
identifier.name = match[3]
res := ResourceOutput{
identifier: identifier,
}
res.status = status.FromStringOrDie(match[4])
res.message = match[5]
currentFrame.Resources = append(currentFrame.Resources, res)
continue
}
tableOutput.UnknownRows = append(tableOutput.UnknownRows, line)
}
tableOutput.Frames = append(tableOutput.Frames, currentFrame)
return tableOutput
}
func createDeploymentStatusFunc() func(*unstructured.Unstructured) error {
metadataMap := map[string]interface{}{
"generation": int64(2),
}
specMap := map[string]interface{}{
"replicas": int64(2),
}
statusMap := map[string]interface{}{
"observedGeneration": int64(2),
"replicas": int64(4),
"updatedReplicas": int64(4),
"readyReplicas": int64(4),
}
var conditions = make([]interface{}, 0)
conditions = append(conditions, map[string]interface{}{
"type": "Available",
"status": "True",
})
callbackCount := int64(0)
return func(deployment *unstructured.Unstructured) error {
_ = unstructured.SetNestedMap(deployment.Object, metadataMap, "metadata")
_ = unstructured.SetNestedMap(deployment.Object, specMap, "spec")
statusMap["availableReplicas"] = callbackCount
_ = unstructured.SetNestedMap(deployment.Object, statusMap, "status")
_ = unstructured.SetNestedSlice(deployment.Object, conditions, "status", "conditions")
callbackCount++
return nil
}
}
func createServiceStatusFunc() func(*unstructured.Unstructured) error {
return func(*unstructured.Unstructured) error {
return nil
}
}
func createPodStatusFunc() func(*unstructured.Unstructured) error {
statusMap := map[string]interface{}{
"phase": "Succeeded",
}
return func(pod *unstructured.Unstructured) error {
_ = unstructured.SetNestedMap(pod.Object, statusMap, "status")
return nil
}
}
type ResourceGetCallback func(resource *unstructured.Unstructured) error
type FakeClient struct {
resourceCallbackMap map[string]ResourceGetCallback
}
func (f *FakeClient) Get(_ context.Context, _ client.ObjectKey, obj runtime.Object) error {
kind := obj.GetObjectKind().GroupVersionKind().Kind
callbackFunc, found := f.resourceCallbackMap[kind]
if !found {
return fmt.Errorf("no callback func found for kind %s", kind)
}
u := obj.(*unstructured.Unstructured)
return callbackFunc(u)
}
func (f *FakeClient) List(context.Context, runtime.Object, ...client.ListOption) error {
return nil
}
func fakeResolver(fakeClient client.Reader, mapperTypes ...schema.GroupVersionKind) newResolverFunc {
return func(pollInterval time.Duration) (*wait.Resolver, meta.RESTMapper, error) {
var groupVersions []schema.GroupVersion
for _, gvk := range mapperTypes {
groupVersions = append(groupVersions, gvk.GroupVersion())
}
mapper := meta.NewDefaultRESTMapper(groupVersions)
for _, gvk := range mapperTypes {
mapper.Add(gvk, meta.RESTScopeNamespace)
}
return wait.NewResolver(fakeClient, mapper, pollInterval), mapper, nil
}
}
func joinStatuses(statuses []status.Status) string {
var stringStatuses []string
for _, s := range statuses {
stringStatuses = append(stringStatuses, s.String())
}
return strings.Join(stringStatuses, ",")
}

View File

@@ -1,356 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package cmd
import (
"fmt"
"io"
"time"
"sigs.k8s.io/kustomize/kstatus/status"
"sigs.k8s.io/kustomize/kstatus/wait"
)
const (
typeColumn = "type"
namespaceColumn = "namespace"
nameColumn = "name"
statusColumn = "status"
messageColumn = "message"
RESET = 0
ESC = 27
RED color = 31
GREEN color = 32
YELLOW color = 33
DEFAULT color = -1 // This is not a valid ANSI escape code. It is used here to mean that no color should be set.
)
type color int
func moveUp(w io.Writer, lineCount int) {
printOrDie(w, "%c[%dA", ESC, lineCount)
}
func eraseCurrentLine(w io.Writer) {
printOrDie(w, "%c[2K\r", ESC)
}
type colorFunc func(s status.Status) color
type contentFunc func(resource ResourceStatusData) string
type tableColumnInfo struct {
header string
width int
colorFunc colorFunc
contentFunc contentFunc
}
func defaultColorFunc(_ status.Status) color {
return DEFAULT
}
var (
tableColumns = map[string]tableColumnInfo{
typeColumn: {
header: "TYPE",
width: 25,
colorFunc: defaultColorFunc,
contentFunc: func(data ResourceStatusData) string {
return fmt.Sprintf("%s/%s", data.Identifier.GroupKind.Group, data.Identifier.GroupKind.Kind)
},
},
namespaceColumn: {
header: "NAMESPACE",
width: 15,
colorFunc: defaultColorFunc,
contentFunc: func(data ResourceStatusData) string {
return data.Identifier.Namespace
},
},
nameColumn: {
header: "NAME",
width: 20,
colorFunc: defaultColorFunc,
contentFunc: func(data ResourceStatusData) string {
return data.Identifier.Name
},
},
statusColumn: {
header: "STATUS",
width: 10,
colorFunc: colorForStatus,
contentFunc: func(data ResourceStatusData) string {
return data.Status.String()
},
},
messageColumn: {
header: "MESSAGE",
width: 40,
colorFunc: defaultColorFunc,
contentFunc: func(data ResourceStatusData) string {
return data.Message
},
},
}
tableColumnOrder = []string{typeColumn, namespaceColumn, nameColumn, statusColumn, messageColumn}
)
type StatusInfo interface {
CurrentStatus() StatusData
}
type StatusData struct {
AggregateStatus status.Status
ResourceStatuses []ResourceStatusData
}
type ResourceStatusData struct {
Identifier wait.ResourceIdentifier
Status status.Status
Message string
}
type TablePrinter struct {
statusInfo StatusInfo
out io.Writer
err io.Writer
showAggStatus bool
}
func newTablePrinter(statusInfo StatusInfo, out io.Writer, err io.Writer, showAggStatus bool) *TablePrinter {
return &TablePrinter{
statusInfo: statusInfo,
out: out,
err: err,
showAggStatus: showAggStatus,
}
}
func (s *TablePrinter) Print() {
s.printTable(s.statusInfo.CurrentStatus(), false)
}
func (s *TablePrinter) PrintUntil(stop <-chan struct{}, interval time.Duration) <-chan struct{} {
completed := make(chan struct{})
s.printTable(s.statusInfo.CurrentStatus(), false)
go func() {
defer close(completed)
ticker := time.NewTicker(interval)
for {
select {
case <-stop:
ticker.Stop()
s.printTable(s.statusInfo.CurrentStatus(), true)
return
case <-ticker.C:
s.printTable(s.statusInfo.CurrentStatus(), true)
}
}
}()
return completed
}
func (s *TablePrinter) printTable(data StatusData, deleteUp bool) {
if deleteUp {
if s.showAggStatus {
moveUp(s.out, 1)
}
moveUp(s.out, 1)
moveUp(s.out, len(data.ResourceStatuses))
}
eraseCurrentLine(s.out)
if s.showAggStatus {
printOrDie(s.out, "AggregateStatus: ")
printWithColorOrDie(s.out, colorForStatus(data.AggregateStatus), "%s\n", data.AggregateStatus)
}
s.printTableRow(headers())
for _, resource := range data.ResourceStatuses {
s.printTableRow(row(resource))
}
}
func (s *TablePrinter) printTableRow(rowData []RowData) {
for i, row := range rowData {
format := fmt.Sprintf("%%-%ds", row.width)
printWithColorOrDie(s.out, row.color, format, trimString(row.content, row.width))
if i != len(rowData)-1 {
printOrDie(s.out, " ")
}
}
printOrDie(s.out, "\n")
}
type RowData struct {
content string
color color
width int
}
func headers() []RowData {
var headers []RowData
for _, columnName := range tableColumnOrder {
column := tableColumns[columnName]
headers = append(headers, RowData{
content: column.header,
color: DEFAULT,
width: column.width,
})
}
return headers
}
func row(resource ResourceStatusData) []RowData {
var row []RowData
for _, columnName := range tableColumnOrder {
column := tableColumns[columnName]
row = append(row, RowData{
content: column.contentFunc(resource),
color: column.colorFunc(resource.Status),
width: column.width,
})
}
return row
}
type eventContentFunc func(wait.Event) string
type eventColumnInfo struct {
header string
width int
requireResourceUpdateEvent bool
contentFunc eventContentFunc
}
var (
eventColumns = []eventColumnInfo{
{
header: "NAMESPACE",
width: 15,
requireResourceUpdateEvent: true,
contentFunc: func(event wait.Event) string {
return event.EventResource.ResourceIdentifier.Namespace
},
},
{
header: "AGG STATUS",
width: 10,
requireResourceUpdateEvent: false,
contentFunc: func(event wait.Event) string {
return event.AggregateStatus.String()
},
},
{
header: "TYPE",
width: 20,
requireResourceUpdateEvent: true,
contentFunc: func(event wait.Event) string {
return event.EventResource.ResourceIdentifier.GroupKind.Kind
},
},
{
header: "NAME",
width: 20,
requireResourceUpdateEvent: true,
contentFunc: func(event wait.Event) string {
return event.EventResource.ResourceIdentifier.Name
},
},
{
header: "STATUS",
width: 10,
requireResourceUpdateEvent: true,
contentFunc: func(event wait.Event) string {
return event.EventResource.Status.String()
},
},
{
header: "MESSAGE",
width: 50,
requireResourceUpdateEvent: false,
contentFunc: func(event wait.Event) string {
switch event.Type {
case wait.ResourceUpdate:
if event.EventResource.Error != nil {
return event.EventResource.Error.Error()
}
return event.EventResource.Message
case wait.Aborted:
return fmt.Sprint("Operation aborted before all resources have become Current")
case wait.Completed:
return fmt.Sprint("All resources have become Current")
}
return ""
},
},
}
)
type EventPrinter struct {
out io.Writer
err io.Writer
}
func newEventPrinter(out io.Writer, err io.Writer) *EventPrinter {
for _, column := range eventColumns {
format := fmt.Sprintf("%%-%ds ", column.width)
printOrDie(out, format, column.header)
}
printOrDie(out, "\n")
return &EventPrinter{
out: out,
err: err,
}
}
func (e *EventPrinter) printEvent(event wait.Event) {
for _, column := range eventColumns {
var text string
if event.Type != wait.ResourceUpdate && column.requireResourceUpdateEvent {
text = ""
} else {
text = trimString(column.contentFunc(event), column.width)
}
format := fmt.Sprintf("%%-%ds ", column.width)
printOrDie(e.out, format, text)
}
printOrDie(e.out, "\n")
}
func printOrDie(w io.Writer, format string, a ...interface{}) {
_, err := fmt.Fprintf(w, format, a...)
if err != nil {
panic(err)
}
}
func printWithColorOrDie(w io.Writer, color color, format string, a ...interface{}) {
if color == DEFAULT {
printOrDie(w, format, a...)
} else {
printOrDie(w, "%c[%dm", ESC, color)
printOrDie(w, format, a...)
printOrDie(w, "%c[%dm", ESC, RESET)
}
}
func colorForStatus(s status.Status) color {
switch s {
case status.CurrentStatus:
return GREEN
case status.InProgressStatus:
return YELLOW
case status.FailedStatus:
return RED
}
return DEFAULT
}
func trimString(str string, maxLength int) string {
if len(str) <= maxLength {
return str
}
return str[:maxLength]
}

View File

@@ -1,99 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package cmd
import (
"time"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
"sigs.k8s.io/kustomize/kstatus/wait"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
var (
scheme = runtime.NewScheme()
)
func init() {
_ = clientgoscheme.AddToScheme(scheme)
}
type newResolverFunc func(pollInterval time.Duration) (*wait.Resolver, meta.RESTMapper, error)
// newResolver returns a new resolver that can resolve status for resources based
// on polling the cluster.
func newResolver(pollInterval time.Duration) (*wait.Resolver, meta.RESTMapper, error) {
config := ctrl.GetConfigOrDie()
mapper, err := apiutil.NewDiscoveryRESTMapper(config)
if err != nil {
return nil, nil, err
}
c, err := client.New(config, client.Options{Scheme: scheme, Mapper: mapper})
if err != nil {
return nil, nil, err
}
return wait.NewResolver(c, mapper, pollInterval), mapper, nil
}
// CaptureIdentifiersFilter implements the Filter interface in the kio package. It
// captures the identifiers for all resources passed through the pipeline.
type CaptureIdentifiersFilter struct {
Identifiers []wait.ResourceIdentifier
Mapper meta.RESTMapper
}
var _ kio.Filter = &CaptureIdentifiersFilter{}
func (f *CaptureIdentifiersFilter) Filter(slice []*yaml.RNode) ([]*yaml.RNode, error) {
for i := range slice {
objectMeta, err := slice[i].GetMeta()
if err != nil {
return nil, err
}
// TODO(mortent): Update kyaml library
id := objectMeta.GetIdentifier()
gv, err := schema.ParseGroupVersion(id.APIVersion)
if err != nil {
return nil, err
}
gk := schema.GroupKind{
Group: gv.Group,
Kind: id.Kind,
}
mapping, err := f.Mapper.RESTMapping(gk)
if err != nil {
return nil, err
}
var namespace string
if mapping.Scope.Name() == meta.RESTScopeNameNamespace && id.Namespace == "" {
namespace = "default"
} else {
namespace = id.Namespace
}
if IsValidKubernetesResource(id) {
f.Identifiers = append(f.Identifiers, wait.ResourceIdentifier{
Name: id.Name,
Namespace: namespace,
GroupKind: schema.GroupKind{
Group: gv.Group,
Kind: id.Kind,
},
})
}
}
return slice, nil
}
func IsValidKubernetesResource(id yaml.ResourceIdentifier) bool {
return id.GetKind() != "" && id.GetAPIVersion() != "" && id.GetName() != ""
}

View File

@@ -1,41 +0,0 @@
package cmd
import (
"testing"
"github.com/stretchr/testify/assert"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
func TestIsValidKubernetesResource(t *testing.T) {
testCases := map[string]struct {
data yaml.ResourceIdentifier
expected bool
}{
"invalid resource": {
data: yaml.ResourceIdentifier{
Name: "",
APIVersion: "",
Kind: "",
Namespace: "",
},
expected: false,
},
"valid resource": {
data: yaml.ResourceIdentifier{
Name: "SomeName",
APIVersion: "SomeVersion",
Kind: "SomeKind",
Namespace: "",
},
expected: true,
},
}
for tn, tc := range testCases {
t.Run(tn, func(t *testing.T) {
assert.Equal(t, IsValidKubernetesResource(tc.data), tc.expected)
})
}
}

View File

@@ -1,205 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package cmd
import (
"context"
"sync"
"time"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/kstatus/status"
"sigs.k8s.io/kustomize/kstatus/wait"
"sigs.k8s.io/kustomize/kustomize/v3/internal/commands/status/generateddocs/commands"
"sigs.k8s.io/kustomize/kyaml/kio"
)
// GetWaitRunner return a command WaitRunner.
func GetWaitRunner() *WaitRunner {
r := &WaitRunner{
newResolverFunc: newResolver,
}
c := &cobra.Command{
Use: "wait DIR...",
Short: commands.WaitShort,
Long: commands.WaitLong,
Example: commands.WaitExamples,
RunE: r.runE,
}
c.Flags().BoolVar(&r.IncludeSubpackages, "include-subpackages", true,
"also print resources from subpackages.")
c.Flags().DurationVar(&r.Interval, "interval", 2*time.Second,
"check every n seconds. Default is every 2 seconds.")
c.Flags().DurationVar(&r.Timeout, "timeout", 60*time.Second,
"give up after n seconds. Default is 60 seconds.")
r.Command = c
return r
}
func WaitCommand() *cobra.Command {
return GetWaitRunner().Command
}
// WaitRunner captures the parameters for the command and contains
// the run function.
type WaitRunner struct {
IncludeSubpackages bool
Interval time.Duration
Timeout time.Duration
Command *cobra.Command
newResolverFunc newResolverFunc
}
// runE implements the logic of the command and will call the Wait command in the wait
// package, use a ResourceStatusCollector to capture the events from the channel, and the
// TablePrinter to display the information.
func (r *WaitRunner) runE(c *cobra.Command, args []string) error {
ctx := context.Background()
resolver, mapper, err := r.newResolverFunc(r.Interval)
if err != nil {
return errors.Wrap(err, "errors creating resolver")
}
captureFilter := &CaptureIdentifiersFilter{
Mapper: mapper,
}
filters := []kio.Filter{captureFilter}
var inputs []kio.Reader
for _, a := range args {
inputs = append(inputs, kio.LocalPackageReader{
PackagePath: a,
IncludeSubpackages: r.IncludeSubpackages,
})
}
if len(inputs) == 0 {
inputs = append(inputs, &kio.ByteReader{Reader: c.InOrStdin()})
}
err = kio.Pipeline{
Inputs: inputs,
Filters: filters,
}.Execute()
if err != nil {
return errors.Wrap(err, "error reading manifests")
}
collector := newResourceStatusCollector(captureFilter.Identifiers)
stop := make(chan struct{})
printer := newTablePrinter(CollectorStatusInfo{collector}, c.OutOrStdout(), c.OutOrStderr(), true)
printFinished := printer.PrintUntil(stop, 1*time.Second)
ctx, cancel := context.WithTimeout(ctx, r.Timeout)
defer cancel()
resChannel := resolver.WaitForStatus(ctx, captureFilter.Identifiers)
for msg := range resChannel {
switch msg.Type {
case wait.ResourceUpdate:
collector.updateResourceStatus(msg)
case wait.Aborted:
collector.updateAggregateStatus(msg.AggregateStatus)
case wait.Completed:
collector.updateAggregateStatus(msg.AggregateStatus)
}
}
close(stop)
<-printFinished // Wait for printer to finish work.
return nil
}
// ResourceStatusCollector captures the latest state seen for all resources
// based on the events from the Wait channel. This is used by the TablePrinter
// to display status for all resources.
type ResourceStatusCollector struct {
mux sync.RWMutex
AggregateStatus status.Status
ResourceStatuses []*ResourceStatus
}
// updateResourceStatus takes the given event and update the status info
// in the ResourceStatusCollector.
func (r *ResourceStatusCollector) updateResourceStatus(msg wait.Event) {
r.mux.Lock()
defer r.mux.Unlock()
r.AggregateStatus = msg.AggregateStatus
eventResource := msg.EventResource
for _, resourceState := range r.ResourceStatuses {
if resourceState.Identifier.GroupKind == eventResource.ResourceIdentifier.GroupKind &&
resourceState.Identifier.Namespace == eventResource.ResourceIdentifier.Namespace &&
resourceState.Identifier.Name == eventResource.ResourceIdentifier.Name {
resourceState.Status = eventResource.Status
resourceState.Message = eventResource.Message
}
}
}
// updateAggregateStatus sets the aggregate status of the ResourceStatusCollector to the
// given value.
func (r *ResourceStatusCollector) updateAggregateStatus(aggregateStatus status.Status) {
r.mux.Lock()
defer r.mux.Unlock()
r.AggregateStatus = aggregateStatus
}
// ResourceStatus contains the status information for a single resource.
type ResourceStatus struct {
Identifier wait.ResourceIdentifier
Status status.Status
Message string
}
// newResourceStatusCollector creates a new ResourceStatusCollector with the given
// resources and sets the status for all of them to Unknown.
func newResourceStatusCollector(identifiers []wait.ResourceIdentifier) *ResourceStatusCollector {
var statuses []*ResourceStatus
for _, id := range identifiers {
statuses = append(statuses, &ResourceStatus{
Identifier: id,
Status: status.UnknownStatus,
Message: "",
})
}
return &ResourceStatusCollector{
AggregateStatus: status.UnknownStatus,
ResourceStatuses: statuses,
}
}
// CollectorStatusInfo is a wrapper around the ResourceStatusCollector
// to make it adhere to the interface of the TableWriter.
type CollectorStatusInfo struct {
Collector *ResourceStatusCollector
}
// CurrentStatus implements the interface for the TableWriter and
// returns a copy of the current status of the resources in the
// ResourceStatusCollector. This is done to make sure the TableWriter
// does not have to deal with synchronization when accessing the data.
func (f CollectorStatusInfo) CurrentStatus() StatusData {
f.Collector.mux.RLock()
defer f.Collector.mux.RUnlock()
var resourceData []ResourceStatusData
for _, res := range f.Collector.ResourceStatuses {
resourceData = append(resourceData, ResourceStatusData{
Identifier: res.Identifier,
Status: res.Status,
Message: res.Message,
})
}
return StatusData{
AggregateStatus: f.Collector.AggregateStatus,
ResourceStatuses: resourceData,
}
}

View File

@@ -1,179 +0,0 @@
package cmd
import (
"bytes"
"fmt"
"reflect"
"testing"
"github.com/acarl005/stripansi"
"github.com/stretchr/testify/assert"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/kustomize/kstatus/status"
)
func TestWaitNoResources(t *testing.T) {
inBuffer := &bytes.Buffer{}
outBuffer := &bytes.Buffer{}
fakeClient := &FakeClient{}
r := GetWaitRunner()
r.newResolverFunc = fakeResolver(fakeClient)
r.Command.SetArgs([]string{})
r.Command.SetIn(inBuffer)
r.Command.SetOut(outBuffer)
err := r.Command.Execute()
if !assert.NoError(t, err) {
return
}
cleanOutput := stripansi.Strip(outBuffer.String())
tableOutput := parseTableOutput(t, cleanOutput)
if want, got := 2, len(tableOutput.Frames); want != got {
t.Errorf("expected %d frames, but found %d", want, got)
}
aggStatuses := tableOutput.allAggStatuses()
expectedAggStatuses := []status.Status{
status.UnknownStatus,
status.CurrentStatus,
}
if !reflect.DeepEqual(aggStatuses, expectedAggStatuses) {
t.Errorf("expected agg statuses to be %s, but got %s", joinStatuses(expectedAggStatuses),
joinStatuses(aggStatuses))
}
resources := tableOutput.resources()
if want, got := 0, len(resources); want != got {
t.Errorf("expected %d resources, but found %d", want, got)
}
}
func TestWaitMultipleUpdates(t *testing.T) {
inBuffer := &bytes.Buffer{}
outBuffer := &bytes.Buffer{}
_, err := fmt.Fprint(inBuffer, `
apiVersion: apps/v1
kind: Deployment
metadata:
name: bar
namespace: default
`)
if !assert.NoError(t, err) {
return
}
fakeClient := &FakeClient{
resourceCallbackMap: map[string]ResourceGetCallback{
"Deployment": createDeploymentStatusFunc(),
},
}
r := GetWaitRunner()
r.newResolverFunc = fakeResolver(fakeClient, appsv1.SchemeGroupVersion.WithKind("Deployment"))
r.Command.SetArgs([]string{})
r.Command.SetIn(inBuffer)
r.Command.SetOut(outBuffer)
err = r.Command.Execute()
if !assert.NoError(t, err) {
return
}
cleanOutput := stripansi.Strip(outBuffer.String())
tableOutput := parseTableOutput(t, cleanOutput)
aggStatuses := tableOutput.dedupedAggStatuses()
expectedStatuses := []status.Status{
status.UnknownStatus,
status.InProgressStatus,
status.CurrentStatus,
}
if !reflect.DeepEqual(aggStatuses, expectedStatuses) {
t.Errorf("expected deduped agg statuses to be %s, but got %s", joinStatuses(expectedStatuses),
joinStatuses(aggStatuses))
}
resources := tableOutput.resources()
if want, got := 1, len(resources); got != want {
t.Errorf("expected %d resource, but got %d", want, got)
}
resource := resources[0]
resourceStatuses := tableOutput.dedupedStatusesForResource(resource)
expectedResourceStatuses := []status.Status{
status.InProgressStatus,
status.CurrentStatus,
}
if !reflect.DeepEqual(expectedResourceStatuses, resourceStatuses) {
t.Errorf("expected resource %q to have statuses %s, but got %s", resource.name,
joinStatuses(expectedResourceStatuses), joinStatuses(resourceStatuses))
}
}
func TestWaitMultipleResources(t *testing.T) {
inBuffer := &bytes.Buffer{}
outBuffer := &bytes.Buffer{}
_, err := fmt.Fprint(inBuffer, `
apiVersion: v1
kind: List
items:
- apiVersion: v1
kind: Pod
metadata:
name: foo
namespace: default
- apiVersion: v1
kind: Service
metadata:
name: bar
namespace: default
`)
if !assert.NoError(t, err) {
return
}
fakeClient := &FakeClient{
resourceCallbackMap: map[string]ResourceGetCallback{
"Pod": createPodStatusFunc(),
"Service": createServiceStatusFunc(),
},
}
r := GetWaitRunner()
r.newResolverFunc = fakeResolver(fakeClient, corev1.SchemeGroupVersion.WithKind("Pod"),
corev1.SchemeGroupVersion.WithKind("Service"))
r.Command.SetArgs([]string{})
r.Command.SetIn(inBuffer)
r.Command.SetOut(outBuffer)
err = r.Command.Execute()
if !assert.NoError(t, err) {
return
}
cleanOutput := stripansi.Strip(outBuffer.String())
tableOutput := parseTableOutput(t, cleanOutput)
aggStatuses := tableOutput.dedupedAggStatuses()
if want, got := status.CurrentStatus, aggStatuses[len(aggStatuses)-1]; want != got {
t.Errorf("expected final agg statuses to be %s, but got %s", want, got)
}
resources := tableOutput.resources()
if want, got := 2, len(resources); got != want {
t.Errorf("expected %d resource, but got %d", want, got)
}
for _, resource := range resources {
resourceStatuses := tableOutput.dedupedStatusesForResource(resource)
if want, got := status.CurrentStatus, resourceStatuses[len(resourceStatuses)-1]; want != got {
t.Errorf("expected resource %q to have final status %s, but got %s", resource.name, want, got)
}
}
}

View File

@@ -1,22 +0,0 @@
## events
[Alpha] Poll the cluster until all provided resources have become Current and list the status change events.
### Synopsis
[Alpha] Poll the cluster for the state of all the provided resources until either they have all become
Current or the timeout is reached. The output will be status change events.
The list of resources which should be polled are provided as manifests either on the filesystem or
on StdIn.
DIR:
Path to local directory. If not provided, input is expected on StdIn.
### Examples
# Read resources from the filesystem and wait up to 1 minute for all of them to become Current
resource status events my-dir/
# Fetch all resources in the cluster and wait up to 5 minutes for all of them to become Current
kubectl get all --all-namespaces -oyaml | resource status events --timeout=5m

View File

@@ -1,21 +0,0 @@
## fetch
[Alpha] Fetch the state of the provided resources from the cluster and display status in a table.
### Synopsis
[Alpha] Fetches the state of all provided resources from the cluster and displays the status in
a table.
The list of resources are provided as manifests either on the filesystem or on StdIn.
DIR:
Path to local directory.
### Examples
# Read resources from the filesystem and wait up to 1 minute for all of them to become Current
resource status fetch my-dir/
# Fetch all resources in the cluster and wait up to 5 minutes for all of them to become Current
kubectl get all --all-namespaces -oyaml | resource status fetch

View File

@@ -1,22 +0,0 @@
## wait
[Alpha] Poll the cluster until all provided resources have become Current and display progress in a table.
### Synopsis
[Alpha] Poll the cluster for the state of all the provided resources until either they have all become
Current or the timeout is reached. The output will be presented as a table.
The list of resources which should be polled are provided as manifests either on the filesystem or
on StdIn.
DIR:
Path to local directory. If not provided, input is expected on StdIn.
### Examples
# Read resources from the filesystem and wait up to 1 minute for all of them to become Current
resource status wait my-dir/
# Fetch all resources in the cluster and wait up to 5 minutes for all of them to become Current
kubectl get all --all-namespaces -oyaml | resource status wait --timeout=5m

View File

@@ -1,55 +0,0 @@
// Code generated by "mdtogo"; DO NOT EDIT.
package commands
var EventsShort = `[Alpha] Poll the cluster until all provided resources have become Current and list the status change events.`
var EventsLong = `
[Alpha] Poll the cluster for the state of all the provided resources until either they have all become
Current or the timeout is reached. The output will be status change events.
The list of resources which should be polled are provided as manifests either on the filesystem or
on StdIn.
DIR:
Path to local directory. If not provided, input is expected on StdIn.
`
var EventsExamples = `
# Read resources from the filesystem and wait up to 1 minute for all of them to become Current
resource status events my-dir/
# Fetch all resources in the cluster and wait up to 5 minutes for all of them to become Current
kubectl get all --all-namespaces -oyaml | resource status events --timeout=5m`
var FetchShort = `[Alpha] Fetch the state of the provided resources from the cluster and display status in a table.`
var FetchLong = `
[Alpha] Fetches the state of all provided resources from the cluster and displays the status in
a table.
The list of resources are provided as manifests either on the filesystem or on StdIn.
DIR:
Path to local directory.
`
var FetchExamples = `
# Read resources from the filesystem and wait up to 1 minute for all of them to become Current
resource status fetch my-dir/
# Fetch all resources in the cluster and wait up to 5 minutes for all of them to become Current
kubectl get all --all-namespaces -oyaml | resource status fetch`
var WaitShort = `[Alpha] Poll the cluster until all provided resources have become Current and display progress in a table. `
var WaitLong = `
[Alpha] Poll the cluster for the state of all the provided resources until either they have all become
Current or the timeout is reached. The output will be presented as a table.
The list of resources which should be polled are provided as manifests either on the filesystem or
on StdIn.
DIR:
Path to local directory. If not provided, input is expected on StdIn.
`
var WaitExamples = `
# Read resources from the filesystem and wait up to 1 minute for all of them to become Current
resource status wait my-dir/
# Fetch all resources in the cluster and wait up to 5 minutes for all of them to become Current
kubectl get all --all-namespaces -oyaml | resource status wait --timeout=5m`

View File

@@ -126,7 +126,6 @@ Example output:
```
kyaml/v0.3.0
api/v0.4.1
kstatus/v0.0.2
cmd/config/v0.3.0
cmd/resource/v0.0.2
cmd/kubectl/v0.1.0

View File

@@ -9,7 +9,7 @@ import (
)
var modules = [...]string{
"kyaml", "api", "kstatus", "cmd/config",
"kyaml", "api", "cmd/config",
"cmd/resource", "cmd/kubectl", "pluginator", "kustomize",
}

View File

@@ -43,7 +43,6 @@ func TestModuleTags(t *testing.T) {
cmd/kubectl/v0.0.3
cmd/resource/v0.0.1
cmd/resource/v0.0.2
kstatus/v0.0.1
kustomize/v3.2.1
kustomize/v3.2.2
kustomize/v3.2.3
@@ -78,12 +77,10 @@ func TestCheckModReplace1(t *testing.T) {
replace (
sigs.k8s.io/kustomize/cmd/kubectl v0.0.3 => ../cmd/kubectl
sigs.k8s.io/kustomize/kstatus v0.0.1 => ../kstatus
)`
expect := `Found replace in testpath
- Please update go.mod to pin a specific version of sigs.k8s.io/kustomize/cmd/kubectl
- Please update go.mod to pin a specific version of sigs.k8s.io/kustomize/kstatus
`
err := checkModReplace(path, []byte(dataString))

View File

@@ -74,7 +74,6 @@ func TestVersionFromGitTags(t *testing.T) {
cmd/kubectl/v0.0.3
cmd/resource/v0.0.1
cmd/resource/v0.0.2
kstatus/v0.0.1
kustomize/v3.2.1
kustomize/v3.2.2
kustomize/v3.2.3