mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-05-24 15:57:00 +00:00
Library for computing status for Kubernetes resources
This commit is contained in:
54
kstatus/.golangci.yml
Normal file
54
kstatus/.golangci.yml
Normal file
@@ -0,0 +1,54 @@
|
||||
# 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
|
||||
2
kstatus/LICENSE_TEMPLATE
Normal file
2
kstatus/LICENSE_TEMPLATE
Normal file
@@ -0,0 +1,2 @@
|
||||
Copyright {{.Year}} {{.Holder}}
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
34
kstatus/Makefile
Normal file
34
kstatus/Makefile
Normal file
@@ -0,0 +1,34 @@
|
||||
# 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 ./...
|
||||
19
kstatus/doc.go
Normal file
19
kstatus/doc.go
Normal file
@@ -0,0 +1,19 @@
|
||||
// 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
|
||||
16
kstatus/go.mod
Normal file
16
kstatus/go.mod
Normal file
@@ -0,0 +1,16 @@
|
||||
module sigs.k8s.io/kustomize/kstatus
|
||||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/ghodss/yaml v1.0.0
|
||||
github.com/kr/pretty v0.1.0 // indirect
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/stretchr/testify v1.4.0
|
||||
golang.org/x/net v0.0.0-20190909003024-a7b16738d86b // indirect
|
||||
golang.org/x/text v0.3.2 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||
sigs.k8s.io/kustomize/pseudo/k8s v0.1.0
|
||||
sigs.k8s.io/yaml v1.1.0
|
||||
)
|
||||
137
kstatus/go.sum
Normal file
137
kstatus/go.sum
Normal file
@@ -0,0 +1,137 @@
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
|
||||
github.com/Azure/go-autorest/autorest v0.9.2/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/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc=
|
||||
github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
|
||||
github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g=
|
||||
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/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM=
|
||||
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/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/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
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/spdystream v0.0.0-20181023171402-6480d4af844c/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
|
||||
github.com/elazarl/goproxy v0.0.0-20191011121108-aa519ddbe484/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
|
||||
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
|
||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
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/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
|
||||
github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
|
||||
github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
|
||||
github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
|
||||
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
|
||||
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
|
||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
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/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||
github.com/gophercloud/gophercloud v0.6.0/go.mod h1:GICNByuaEBibcjmjvI7QvYJSZEbGkcYwAR7EZK2WMqM=
|
||||
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
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/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/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/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
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/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||
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/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc=
|
||||
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/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.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/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=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/net v0.0.0-20170114055629-f2499483f923/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-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190909003024-a7b16738d86b h1:XfVGCX+0T4WOStkaOsJRllbsiImhB2jgVBGc9L0lPGc=
|
||||
golang.org/x/net v0.0.0-20190909003024-a7b16738d86b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
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.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-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=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
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-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
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=
|
||||
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/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 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-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
|
||||
k8s.io/utils v0.0.0-20191030222137-2b95a09bc58d/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
|
||||
sigs.k8s.io/kustomize/pseudo/k8s v0.1.0 h1:otg4dLFc03c3gzl+2CV8GPGcd1kk8wjXwD+UhhcCn5I=
|
||||
sigs.k8s.io/kustomize/pseudo/k8s v0.1.0/go.mod h1:bl/gVJgYYhJZCZdYU2BfnaKYAlqFkgbJEkpl302jEss=
|
||||
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
|
||||
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
490
kstatus/status/core.go
Normal file
490
kstatus/status/core.go
Normal file
@@ -0,0 +1,490 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package status
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
corev1 "sigs.k8s.io/kustomize/pseudo/k8s/api/core/v1"
|
||||
"sigs.k8s.io/kustomize/pseudo/k8s/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,
|
||||
"apps/Deployment": deploymentConditions,
|
||||
"apps/ReplicaSet": replicasetConditions,
|
||||
"policy/PodDisruptionBudget": pdbConditions,
|
||||
"batch/CronJob": alwaysReady,
|
||||
"ConfigMap": alwaysReady,
|
||||
"batch/Job": jobConditions,
|
||||
}
|
||||
|
||||
const (
|
||||
tooFewReady = "LessReady"
|
||||
tooFewAvailable = "LessAvailable"
|
||||
tooFewUpdated = "LessUpdated"
|
||||
tooFewReplicas = "LessReplicas"
|
||||
)
|
||||
|
||||
// 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 == "ondelete" {
|
||||
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
|
||||
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)
|
||||
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)
|
||||
statusReplicas := GetIntField(obj, ".status.replicas", 0)
|
||||
readyReplicas := GetIntField(obj, ".status.readyReplicas", 0)
|
||||
availableReplicas := GetIntField(obj, ".status.availableReplicas", 0)
|
||||
labelledReplicas := GetIntField(obj, ".status.labelledReplicas", 0)
|
||||
|
||||
if specReplicas == 0 && labelledReplicas == 0 && availableReplicas == 0 && readyReplicas == 0 {
|
||||
message := ".spec.replica is 0"
|
||||
return newInProgressStatus("ZeroReplicas", message), nil
|
||||
}
|
||||
|
||||
if specReplicas > labelledReplicas {
|
||||
message := fmt.Sprintf("Labelled: %d/%d", labelledReplicas, 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 return standardized Conditions for Deployment
|
||||
func pdbConditions(u *unstructured.Unstructured) (*Result, error) {
|
||||
obj := u.UnstructuredContent()
|
||||
|
||||
// replicas
|
||||
currentHealthy := GetIntField(obj, ".status.currentHealthy", 0)
|
||||
desiredHealthy := GetIntField(obj, ".status.desiredHealthy", 0)
|
||||
if desiredHealthy == 0 {
|
||||
message := "Missing or zero .status.desiredHealthy"
|
||||
return newInProgressStatus("ZeroDesiredHealthy", message), nil
|
||||
}
|
||||
if desiredHealthy > currentHealthy {
|
||||
message := fmt.Sprintf("Budget not met. healthy replicas: %d/%d", currentHealthy, desiredHealthy)
|
||||
return newInProgressStatus("BudgetNotMet", message), nil
|
||||
}
|
||||
|
||||
// All ok
|
||||
return &Result{
|
||||
Status: CurrentStatus,
|
||||
Message: fmt.Sprintf("Budget is met. Replicas: %d/%d", currentHealthy, desiredHealthy),
|
||||
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
|
||||
}
|
||||
38
kstatus/status/doc.go
Normal file
38
kstatus/status/doc.go
Normal file
@@ -0,0 +1,38 @@
|
||||
// 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 is:
|
||||
// * 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
|
||||
110
kstatus/status/example_test.go
Normal file
110
kstatus/status/example_test.go
Normal file
@@ -0,0 +1,110 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package status_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
. "sigs.k8s.io/kustomize/kstatus/status"
|
||||
"sigs.k8s.io/kustomize/pseudo/k8s/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"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)
|
||||
}
|
||||
92
kstatus/status/generic.go
Normal file
92
kstatus/status/generic.go
Normal file
@@ -0,0 +1,92 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package status
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
corev1 "sigs.k8s.io/kustomize/pseudo/k8s/api/core/v1"
|
||||
"sigs.k8s.io/kustomize/pseudo/k8s/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
|
||||
}
|
||||
|
||||
// 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, errors.New("unable to find metadata.generation from resource")
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
161
kstatus/status/status.go
Normal file
161
kstatus/status/status.go
Normal file
@@ -0,0 +1,161 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package status
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
corev1 "sigs.k8s.io/kustomize/pseudo/k8s/api/core/v1"
|
||||
"sigs.k8s.io/kustomize/pseudo/k8s/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"
|
||||
)
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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 || res != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
166
kstatus/status/status_augment_test.go
Normal file
166
kstatus/status/status_augment_test.go
Normal file
@@ -0,0 +1,166 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package status
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
corev1 "sigs.k8s.io/kustomize/pseudo/k8s/api/core/v1"
|
||||
"sigs.k8s.io/kustomize/pseudo/k8s/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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
1273
kstatus/status/status_compute_test.go
Normal file
1273
kstatus/status/status_compute_test.go
Normal file
File diff suppressed because it is too large
Load Diff
112
kstatus/status/util.go
Normal file
112
kstatus/status/util.go
Normal file
@@ -0,0 +1,112 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package status
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
corev1 "sigs.k8s.io/kustomize/pseudo/k8s/api/core/v1"
|
||||
apiunstructured "sigs.k8s.io/kustomize/pseudo/k8s/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"sigs.k8s.io/kustomize/pseudo/k8s/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
|
||||
}
|
||||
59
kstatus/status/util_test.go
Normal file
59
kstatus/status/util_test.go
Normal file
@@ -0,0 +1,59 @@
|
||||
// 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")
|
||||
}
|
||||
Reference in New Issue
Block a user