mirror of https://github.com/fluxcd/cli-utils.git
address comments and add tests for pruning
This commit is contained in:
parent
68698efcbe
commit
f3ca02a96a
|
|
@ -0,0 +1,28 @@
|
|||
FROM golang:alpine AS builder
|
||||
|
||||
# Install git
|
||||
RUN apk update && apk add --no-cache git
|
||||
|
||||
WORKDIR $GOPATH/src/cli-experimental
|
||||
COPY . .
|
||||
|
||||
# Install wire
|
||||
RUN GO111MODULE=off go get github.com/google/wire/cmd/wire
|
||||
|
||||
ENV CGO_ENABLED=0
|
||||
|
||||
# Run go generate
|
||||
RUN GO111MODULE=on go generate
|
||||
|
||||
# Build binary
|
||||
RUN GO111MODULE=on go build -o /go/bin/k2
|
||||
|
||||
FROM scratch
|
||||
|
||||
# Copy the executable.
|
||||
COPY --from=builder /go/bin/k2 /go/bin/k2
|
||||
|
||||
# Run the binary
|
||||
ENTRYPOINT ["go/bin/k2"]
|
||||
|
||||
|
||||
|
|
@ -30,8 +30,18 @@ func GetApplyCommand(a util.Args) *cobra.Command {
|
|||
cmd := &cobra.Command{
|
||||
Use: "apply",
|
||||
Short: "Apply resource configurations.",
|
||||
Long: `Apply resource configurations to k8s cluster.`,
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Long: `Apply resource configurations to k8s cluster.
|
||||
The resource configurations can be from a Kustomization directory.
|
||||
The path of the resource configurations should be passed to apply
|
||||
as an argument.
|
||||
|
||||
# Apply the configurations from a directory containing kustomization.yaml - e.g. dir/kustomization.yaml
|
||||
k2 apply dir
|
||||
|
||||
When server-side apply is available on the cluster, it is used; otherwise, client-side apply
|
||||
is used.
|
||||
`,
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
}
|
||||
|
||||
cmd.RunE = func(cmd *cobra.Command, args []string) error {
|
||||
|
|
|
|||
|
|
@ -28,9 +28,11 @@ import (
|
|||
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
||||
func Execute(args []string, fn func(*cobra.Command)) error {
|
||||
rootCmd := &cobra.Command{
|
||||
Use: "k2",
|
||||
// TODO(Liujingfang1): change this binary to a better name
|
||||
Use: "cli-experimental",
|
||||
Short: "kubectl version 2",
|
||||
Long: `kubectl version 2`,
|
||||
Long: `kubectl version 2
|
||||
with commands apply, prune, delete and dynamic commands`,
|
||||
}
|
||||
if fn != nil {
|
||||
fn(rootCmd)
|
||||
|
|
|
|||
|
|
@ -26,9 +26,16 @@ import (
|
|||
func GetDeleteCommand(a util.Args) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "delete",
|
||||
Short: "Delete resources from a K8s cluster.",
|
||||
Long: `Delete resources from a K8s cluster.`,
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Short: "Delete resources from a Kubernetes cluster.",
|
||||
Long: `Delete resources from a Kubernetes cluster.
|
||||
The resource configurations can be from a Kustomization directory.
|
||||
The path of the resource configurations should be passed to delete
|
||||
as an argument.
|
||||
|
||||
# Delete the configurations from a directory containing kustomization.yaml - e.g. dir/kustomization.yaml
|
||||
k2 delete dir
|
||||
`,
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
}
|
||||
|
||||
cmd.RunE = func(cmd *cobra.Command, args []string) error {
|
||||
|
|
|
|||
|
|
@ -28,9 +28,49 @@ func GetPruneCommand(a util.Args) *cobra.Command {
|
|||
Use: "prune",
|
||||
Short: "Prune obsolete resources.",
|
||||
Long: `Prune obsolete resources.
|
||||
It is based on checking the inventory annotation that
|
||||
is stored in the resource configuration that is passed
|
||||
to prune command.`,
|
||||
# Prune the configurations from a directory containing kustomization.yaml -e.g. dir/kustomization.yaml
|
||||
k2 prune dir
|
||||
|
||||
The pruning is done based on checking the inventory annotation that
|
||||
is stored in the resource configuration that is passed to prune command.
|
||||
The inventory annotation kustomize.k8s.io/Inventory has following format:
|
||||
{
|
||||
"current":
|
||||
{
|
||||
"apps_v1_Deployment|default|mysql":null,
|
||||
"~G_v1_Secret|default|pass-dfg7h97cf6":
|
||||
[
|
||||
{
|
||||
"group":"apps",
|
||||
"version":"v1",
|
||||
"kind":"Deployment",
|
||||
"name":"mysql",
|
||||
"namespace":"default",
|
||||
}
|
||||
],
|
||||
"~G_v1_Service|default|mysql":null
|
||||
}
|
||||
}
|
||||
"previous:
|
||||
{
|
||||
"apps_v1_Deployment|default|mysql":null,
|
||||
"~G_v1_Secret|default|pass-dfg7h97cf6":
|
||||
[
|
||||
{
|
||||
"group":"apps",
|
||||
"version":"v1",
|
||||
"kind":"Deployment",
|
||||
"name":"mysql",
|
||||
"namespace":"default",
|
||||
}
|
||||
],
|
||||
"~G_v1_Service|default|mysql":null
|
||||
}
|
||||
}
|
||||
}
|
||||
Any objects in the previous part that don't show up in the current part will be pruned.
|
||||
For more information, see https://github.com/kubernetes-sigs/kustomize/blob/master/docs/inventory_object.md.
|
||||
`,
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
}
|
||||
|
||||
|
|
|
|||
1
go.mod
1
go.mod
|
|
@ -27,7 +27,6 @@ require (
|
|||
github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.8.5 // indirect
|
||||
github.com/imdario/mergo v0.3.7 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/jgautheron/goconst v0.0.0-20170703170152-9740945f5dcb // indirect
|
||||
github.com/json-iterator/go v1.1.6 // indirect
|
||||
github.com/mdempsky/maligned v0.0.0-20180708014732-6e39bd26a8c8 // indirect
|
||||
|
|
|
|||
13
go.sum
13
go.sum
|
|
@ -33,6 +33,7 @@ github.com/alexkohler/nakedret v0.0.0-20190321114339-98ae56e4e0f3/go.mod h1:tfDQ
|
|||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.0 h1:LzQXZOgg4CQfE6bFvXGM30YZL1WW/M337pXml+GrcZ4=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
|
|
@ -124,6 +125,7 @@ github.com/grpc-ecosystem/grpc-gateway v1.8.5 h1:2+KSC78XiO6Qy0hIjfc1OD9H+hsaJdJ
|
|||
github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
|
||||
github.com/hashicorp/golang-lru v0.5.0/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.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI=
|
||||
|
|
@ -135,6 +137,7 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i
|
|||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jgautheron/goconst v0.0.0-20170703170152-9740945f5dcb h1:D5s1HIu80AcMGcqmk7fNIVptmAubVHHaj3v5Upex6Zs=
|
||||
github.com/jgautheron/goconst v0.0.0-20170703170152-9740945f5dcb/go.mod h1:82TxjOpWQiPmywlbIaB2ZkqJoSYJdLGPgAJDvM3PbKc=
|
||||
github.com/json-iterator/go v0.0.0-20180315132816-ca39e5af3ece/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
|
|
@ -154,6 +157,7 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|||
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mailru/easyjson v0.0.0-20180606163543-3fdea8d05856/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3bUBu+FXuk2pFbkN6tcwi/pjyaDic=
|
||||
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
|
|
@ -166,6 +170,7 @@ github.com/mibk/dupl v1.0.0/go.mod h1:pCr4pNxxIbFGvtyCOi0c7LVjmV6duhKWV+ex5vh38M
|
|||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
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-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-20180228065516-1df9eeb2bb81/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
|
|
@ -190,6 +195,7 @@ github.com/openzipkin/zipkin-go v0.1.3/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTm
|
|||
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
||||
github.com/pelletier/go-buffruneio v0.2.0 h1:U4t4R6YkofJ5xHm3dJzuRpPZ0mr5MMCoAWooScCR7aA=
|
||||
github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
|
|
@ -215,12 +221,16 @@ github.com/securego/gosec v0.0.0-20190510081509-ee80733faf72/go.mod h1:shk+oGa7J
|
|||
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.2/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
|
||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4=
|
||||
github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
|
|
@ -239,6 +249,7 @@ github.com/walle/lll v0.0.0-20160702150637-8b13b3fbf731 h1:b7JCW5NchRssZBlI5RffU
|
|||
github.com/walle/lll v0.0.0-20160702150637-8b13b3fbf731/go.mod h1:OjXnoVXDAiJx16YuOKsfCCcyNAFO+fj/Ocwtvh0K5SU=
|
||||
github.com/xanzy/ssh-agent v0.2.0 h1:Adglfbi5p9Z0BmK2oKU9nTG+zKfniSfnaMYB+ULd+Ro=
|
||||
github.com/xanzy/ssh-agent v0.2.0/go.mod h1:0NyE30eGUDliuLEHJgYte/zncp2zdTStcOnWhgSqHD8=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
go.opencensus.io v0.19.1/go.mod h1:gug0GbSHa8Pafr0d2urOSgoXHZ6x/RUlaiT0d9pqb4A=
|
||||
go.opencensus.io v0.19.2 h1:ZZpq6xI6kv/LuE/5s5UQvBU5vMjvRnPb8PvJrIntAnc=
|
||||
go.opencensus.io v0.19.2/go.mod h1:NO/8qkisMZLZ1FCsKNqtJPwc8/TaclWyY0B6wcYNg9M=
|
||||
|
|
@ -249,6 +260,7 @@ go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/
|
|||
go.uber.org/zap v1.9.1 h1:XCJQEf3W6eZaVwhRBof6ImoYGJSITeKWsyeh3HFu/5o=
|
||||
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
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=
|
||||
|
|
@ -298,6 +310,7 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h
|
|||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/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-20181218192612-074acd46bca6/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 h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
|
||||
|
|
|
|||
|
|
@ -31,10 +31,18 @@ import (
|
|||
|
||||
// Apply applies directories
|
||||
type Apply struct {
|
||||
// DynamicClient is the client used to talk
|
||||
// with the cluster
|
||||
DynamicClient client.Client
|
||||
Out io.Writer
|
||||
Resources clik8s.ResourceConfigs
|
||||
Commit *object.Commit
|
||||
|
||||
// Out stores the output
|
||||
Out io.Writer
|
||||
|
||||
// Resources is a list of resource configurations
|
||||
Resources clik8s.ResourceConfigs
|
||||
|
||||
// Commit is a git commit object
|
||||
Commit *object.Commit
|
||||
}
|
||||
|
||||
// Result contains the Apply Result
|
||||
|
|
@ -49,7 +57,7 @@ func (a *Apply) Do() (Result, error) {
|
|||
// TODO(Liuijngfang1): add a dry-run for all objects
|
||||
// When the dry-run passes, proceed to the actual apply
|
||||
|
||||
for _, u := range adjustOrder(a.Resources) {
|
||||
for _, u := range normalizeResourceOrdering(a.Resources) {
|
||||
annotation := u.GetAnnotations()
|
||||
_, ok := annotation[inventory.InventoryAnnotation]
|
||||
|
||||
|
|
@ -113,8 +121,8 @@ func mergeInventoryAnnotation(newObj, oldObj *unstructured.Unstructured) (*unstr
|
|||
return newObj, nil
|
||||
}
|
||||
|
||||
// adjustOrder moves the inventory object to be the first resource
|
||||
func adjustOrder(resources clik8s.ResourceConfigs) []*unstructured.Unstructured {
|
||||
// normalizeResourceOrdering moves the inventory object to be the first resource
|
||||
func normalizeResourceOrdering(resources clik8s.ResourceConfigs) []*unstructured.Unstructured {
|
||||
var results []*unstructured.Unstructured
|
||||
index := -1
|
||||
for i, u := range resources {
|
||||
|
|
|
|||
|
|
@ -334,7 +334,10 @@ var _ = Describe("Client", func() {
|
|||
By("encoding the deployment as unstructured")
|
||||
u := &unstructured.Unstructured{}
|
||||
scheme.Convert(dep, u, nil)
|
||||
annotation, err := patch.GetModifiedConfiguration(u, true)
|
||||
data, err := patch.SerializeLastApplied(u, true)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
expected := u.DeepCopy()
|
||||
err = runtime.DecodeInto(unstructured.UnstructuredJSONScheme, data, expected)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
u.SetGroupVersionKind(schema.GroupVersionKind{
|
||||
Group: "apps",
|
||||
|
|
@ -349,7 +352,8 @@ var _ = Describe("Client", func() {
|
|||
actual, err := clientset.AppsV1().Deployments(ns).Get(dep.Name, metav1.GetOptions{})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(actual).NotTo(BeNil())
|
||||
Expect(actual.Annotations[v1.LastAppliedConfigAnnotation]).To(Equal(string(annotation)))
|
||||
|
||||
Expect(actual.Annotations[v1.LastAppliedConfigAnnotation]).To(Equal(expected.GetAnnotations()[v1.LastAppliedConfigAnnotation]))
|
||||
close(done)
|
||||
})
|
||||
|
||||
|
|
@ -379,7 +383,7 @@ var _ = Describe("Client", func() {
|
|||
Version: "v1",
|
||||
})
|
||||
u.SetAnnotations(map[string]string{"foo": "bar"})
|
||||
annotation, err := patch.GetModifiedConfiguration(u, false)
|
||||
annotation, err := patch.SerializeLastApplied(u, false)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = cl.Apply(context.TODO(), u)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
|
|
|||
|
|
@ -31,10 +31,18 @@ import (
|
|||
|
||||
// Delete applies directories
|
||||
type Delete struct {
|
||||
// DynamicClient is the client used to talk
|
||||
// with the cluster
|
||||
DynamicClient client.Client
|
||||
Out io.Writer
|
||||
Resources clik8s.ResourceConfigs
|
||||
Commit *object.Commit
|
||||
|
||||
// Out stores the output
|
||||
Out io.Writer
|
||||
|
||||
// Resources is a list of resource configurations
|
||||
Resources clik8s.ResourceConfigs
|
||||
|
||||
// Commit is a git commit object
|
||||
Commit *object.Commit
|
||||
}
|
||||
|
||||
// Result contains the Apply Result
|
||||
|
|
@ -45,18 +53,19 @@ type Result struct {
|
|||
// Do executes the delete
|
||||
func (a *Delete) Do() (Result, error) {
|
||||
fmt.Fprintf(a.Out, "Doing `cli-experimental delete`\n")
|
||||
for _, u := range adjustOrder(a.Resources) {
|
||||
ctx := context.Background()
|
||||
for _, u := range normalizeResourceOrdering(a.Resources) {
|
||||
annotations := u.GetAnnotations()
|
||||
_, ok := annotations[inventory.InventoryAnnotation]
|
||||
if ok {
|
||||
err := a.deleteLeftOvers(annotations)
|
||||
err := a.handleInventroy(ctx, annotations)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to delete leftovers for inventory %v\n", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
err := a.deleteObject(u.GroupVersionKind(), u.GetNamespace(), u.GetName())
|
||||
err := a.deleteObject(ctx, u.GroupVersionKind(), u.GetNamespace(), u.GetName())
|
||||
if err != nil {
|
||||
fmt.Fprint(os.Stderr, err)
|
||||
}
|
||||
|
|
@ -65,7 +74,12 @@ func (a *Delete) Do() (Result, error) {
|
|||
return Result{Resources: a.Resources}, nil
|
||||
}
|
||||
|
||||
func (a *Delete) deleteLeftOvers(annotations map[string]string) error {
|
||||
// handleInventory reads the inventory annotation
|
||||
// and delete any object recorded in it that hasn't been deleted.
|
||||
// When there is an inventory object in the resource configurations, the inventory
|
||||
// object may record some objects that are applied previously and never been pruned.
|
||||
// By delete command, those objects are supposed to be cleaned up as well.
|
||||
func (a *Delete) handleInventroy(ctx context.Context, annotations map[string]string) error {
|
||||
inv := inventory.NewInventory()
|
||||
err := inv.LoadFromAnnotation(annotations)
|
||||
if err != nil {
|
||||
|
|
@ -80,7 +94,7 @@ func (a *Delete) deleteLeftOvers(annotations map[string]string) error {
|
|||
Version: id.Version,
|
||||
Kind: id.Kind,
|
||||
}
|
||||
err = a.deleteObject(gvk, id.Namespace, id.Name)
|
||||
err = a.deleteObject(ctx, gvk, id.Namespace, id.Name)
|
||||
if err != nil {
|
||||
fmt.Fprint(os.Stderr, err)
|
||||
}
|
||||
|
|
@ -88,13 +102,13 @@ func (a *Delete) deleteLeftOvers(annotations map[string]string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (a *Delete) deleteObject(gvk schema.GroupVersionKind, ns, nm string) error {
|
||||
func (a *Delete) deleteObject(ctx context.Context, gvk schema.GroupVersionKind, ns, nm string) error {
|
||||
obj := &unstructured.Unstructured{}
|
||||
obj.SetGroupVersionKind(gvk)
|
||||
obj.SetNamespace(ns)
|
||||
obj.SetName(nm)
|
||||
|
||||
err := a.DynamicClient.Delete(context.Background(), obj, &metav1.DeleteOptions{})
|
||||
err := a.DynamicClient.Delete(ctx, obj, &metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
return nil
|
||||
|
|
@ -104,8 +118,9 @@ func (a *Delete) deleteObject(gvk schema.GroupVersionKind, ns, nm string) error
|
|||
return nil
|
||||
}
|
||||
|
||||
// adjustOrder move the inventory object to be the last resource
|
||||
func adjustOrder(resources clik8s.ResourceConfigs) []*unstructured.Unstructured {
|
||||
// normalizeResourceOrdering move the inventory object to be the last resource
|
||||
// This is to make sure the inventory object is the last object to be deleted.
|
||||
func normalizeResourceOrdering(resources clik8s.ResourceConfigs) []*unstructured.Unstructured {
|
||||
var results []*unstructured.Unstructured
|
||||
index := -1
|
||||
for i, u := range resources {
|
||||
|
|
|
|||
|
|
@ -18,12 +18,10 @@ import (
|
|||
"context"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gopkg.in/src-d/go-git.v4/plumbing/object"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"sigs.k8s.io/cli-experimental/internal/pkg/clik8s"
|
||||
"sigs.k8s.io/cli-experimental/internal/pkg/delete"
|
||||
"sigs.k8s.io/cli-experimental/internal/pkg/wirecli/wiretest"
|
||||
|
|
@ -39,7 +37,7 @@ func TestDeleteEmpty(t *testing.T) {
|
|||
assert.Equal(t, delete.Result{}, r)
|
||||
}
|
||||
|
||||
func TestPrune(t *testing.T) {
|
||||
func TestDelete(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
kp := wiretest.InitializConfigProvider()
|
||||
fs, cleanup, err := wiretest.InitializeKustomization()
|
||||
|
|
@ -65,7 +63,7 @@ func TestPrune(t *testing.T) {
|
|||
Kind: "ConfigMapList",
|
||||
Version: "v1",
|
||||
})
|
||||
err = a.DynamicClient.List(context.Background(), cmList, "default", metav1.ListOptions{})
|
||||
err = a.DynamicClient.List(context.Background(), cmList, "default", nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(cmList.Items), 3)
|
||||
|
||||
|
|
@ -76,7 +74,7 @@ func TestPrune(t *testing.T) {
|
|||
_, err = d.Do()
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = d.DynamicClient.List(context.Background(), cmList, "default", metav1.ListOptions{})
|
||||
err = d.DynamicClient.List(context.Background(), cmList, "default", nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(cmList.Items), 0)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,3 +12,79 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
package prune
|
||||
|
||||
/*
|
||||
Prune delete objects based on the inventory annotation, which has following format
|
||||
|
||||
kustomize.k8s.io/Inventory:
|
||||
{
|
||||
"current":
|
||||
{
|
||||
"apps_v1_Deployment|default|mysql":null,
|
||||
"~G_v1_Secret|default|pass-dfg7h97cf6":
|
||||
[
|
||||
{
|
||||
"group":"apps",
|
||||
"version":"v1",
|
||||
"kind":"Deployment",
|
||||
"name":"mysql",
|
||||
"namespace":"default",
|
||||
}
|
||||
],
|
||||
"~G_v1_Service|default|mysql":null
|
||||
}
|
||||
}
|
||||
"previous:
|
||||
{
|
||||
"apps_v1_Deployment|default|mysql":null,
|
||||
"~G_v1_Secret|default|pass-dfg7h97cf6":
|
||||
[
|
||||
{
|
||||
"group":"apps",
|
||||
"version":"v1",
|
||||
"kind":"Deployment",
|
||||
"name":"mysql",
|
||||
"namespace":"default",
|
||||
}
|
||||
],
|
||||
"~G_v1_Service|default|mysql":null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Kusotmize.k8s.io/InventoryHash: <some hash>
|
||||
|
||||
|
||||
This inventory annotation is generated automatically by Kustomize when `inventory` directive is
|
||||
present in the kustomization.yaml file.
|
||||
|
||||
inventory:
|
||||
type: ConfigMap
|
||||
configMap:
|
||||
name: my-inventory
|
||||
namespace: default
|
||||
|
||||
The inventory object generated by Kustomize only contains "current" part in the annotation.
|
||||
When this object is passed to apply, prune and delete, it doesn't contains the "previous" part.
|
||||
The existing inventory object on cluster can contain both the "current" part and "previous" part.
|
||||
|
||||
Apply reads the existing inventory object and merge its "current" into "previous". The "current" is then set
|
||||
to be the input inventory object's "current".
|
||||
|
||||
Prune reads the existing inventory object and delete any object that satisfies following conditions:
|
||||
- It does not show up in the "current" part
|
||||
- It does not referred by any object that shows up in the "current" part
|
||||
|
||||
The tests in this package covers following cases:
|
||||
- A kustomization with one ConfigMap, which is not referred. When the name of this ConfigMap changes, prune
|
||||
deletes the previous ConfigMap
|
||||
- A kustomization with one Secret and one Deployment, where the Secret is referred in the Deployment.
|
||||
When the name of the Secret changes, prune doesn't delete the previous Secrets since the Deployment
|
||||
object still exists.
|
||||
- A kustomization with one Secret and one StatefulSet, where the Secret is referred in the StatefulSet.
|
||||
The case is the same as the above. When the name of the Secret changes, prune doesn't delete the previous
|
||||
Secrets.
|
||||
- A kustomization with multiple objects. When a different namePrefix is added, all objects in that
|
||||
Kustomization have a different name. Prune deletes the previous set of objects and only keep the objects
|
||||
with the latest nameprefix.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -31,12 +31,22 @@ import (
|
|||
"sigs.k8s.io/kustomize/pkg/inventory"
|
||||
)
|
||||
|
||||
// Prune prunes directories
|
||||
// Prune prunes obsolete resources from a kustomization directory
|
||||
// that are applied in previous applies but not show up in the
|
||||
// latest apply.
|
||||
type Prune struct {
|
||||
// DynamicClient is the client used to talk
|
||||
// with the cluster
|
||||
DynamicClient client.Client
|
||||
Out io.Writer
|
||||
Resources clik8s.ResourcePruneConfigs
|
||||
Commit *object.Commit
|
||||
|
||||
// Out stores the output
|
||||
Out io.Writer
|
||||
|
||||
// Resources is the resource used for pruning
|
||||
Resources clik8s.ResourcePruneConfigs
|
||||
|
||||
// Commit is a git commit object
|
||||
Commit *object.Commit
|
||||
}
|
||||
|
||||
// Result contains the Prune Result
|
||||
|
|
@ -50,6 +60,7 @@ func (o *Prune) Do() (Result, error) {
|
|||
return Result{}, nil
|
||||
}
|
||||
fmt.Fprintf(o.Out, "Doing `cli-experimental prune`\n")
|
||||
ctx := context.Background()
|
||||
|
||||
u := (*unstructured.Unstructured)(o.Resources)
|
||||
annotation := u.GetAnnotations()
|
||||
|
|
@ -59,7 +70,7 @@ func (o *Prune) Do() (Result, error) {
|
|||
}
|
||||
|
||||
obj := u.DeepCopy()
|
||||
err := o.DynamicClient.Get(context.Background(),
|
||||
err := o.DynamicClient.Get(ctx,
|
||||
types.NamespacedName{Namespace: u.GetNamespace(), Name: u.GetName()}, obj)
|
||||
|
||||
if err != nil {
|
||||
|
|
@ -70,7 +81,7 @@ func (o *Prune) Do() (Result, error) {
|
|||
fmt.Fprintf(os.Stderr, "retrieving current configuration of %s from server for %v", u.GetName(), err)
|
||||
return Result{}, err
|
||||
}
|
||||
obj, results, err := o.runPrune(obj)
|
||||
obj, results, err := o.runPrune(ctx, obj)
|
||||
if err != nil {
|
||||
return Result{}, err
|
||||
}
|
||||
|
|
@ -90,7 +101,7 @@ func (o *Prune) Do() (Result, error) {
|
|||
// https://github.com/kubernetes-sigs/kustomize/tree/master/pkg/inventory
|
||||
// This is based on the KEP
|
||||
// https://github.com/kubernetes/enhancements/pull/810
|
||||
func (o *Prune) runPrune(obj *unstructured.Unstructured) (
|
||||
func (o *Prune) runPrune(ctx context.Context, obj *unstructured.Unstructured) (
|
||||
*unstructured.Unstructured, []*unstructured.Unstructured, error) {
|
||||
var results []*unstructured.Unstructured
|
||||
annotations := obj.GetAnnotations()
|
||||
|
|
@ -103,7 +114,7 @@ func (o *Prune) runPrune(obj *unstructured.Unstructured) (
|
|||
Version: item.Version,
|
||||
Kind: item.Kind,
|
||||
}
|
||||
u, err := o.deleteObject(gvk, item.Namespace, item.Name)
|
||||
u, err := o.deleteObject(ctx, gvk, item.Namespace, item.Name)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
|
@ -116,7 +127,8 @@ func (o *Prune) runPrune(obj *unstructured.Unstructured) (
|
|||
return obj, results, nil
|
||||
}
|
||||
|
||||
func (o *Prune) deleteObject(gvk schema.GroupVersionKind, ns, nm string) (*unstructured.Unstructured, error) {
|
||||
func (o *Prune) deleteObject(ctx context.Context, gvk schema.GroupVersionKind,
|
||||
ns, nm string) (*unstructured.Unstructured, error) {
|
||||
obj := &unstructured.Unstructured{}
|
||||
obj.SetGroupVersionKind(gvk)
|
||||
obj.SetNamespace(ns)
|
||||
|
|
|
|||
|
|
@ -15,10 +15,16 @@ package prune_test
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gopkg.in/src-d/go-git.v4/plumbing/object"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"sigs.k8s.io/cli-experimental/internal/pkg/clik8s"
|
||||
"sigs.k8s.io/cli-experimental/internal/pkg/prune"
|
||||
"sigs.k8s.io/cli-experimental/internal/pkg/wirecli/wiretest"
|
||||
|
|
@ -34,7 +40,12 @@ func TestPruneEmpty(t *testing.T) {
|
|||
assert.Equal(t, prune.Result{}, r)
|
||||
}
|
||||
|
||||
func TestPrune(t *testing.T) {
|
||||
/* TestPruneWithoutInventory takes following steps
|
||||
1. create a Kustomization with a ConfigMapGenerator and an inventory object
|
||||
6. run prune
|
||||
7. confirm that no object is pruned since there is no existing inventory object
|
||||
*/
|
||||
func TestPruneWithoutInventory(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
kp := wiretest.InitializConfigProvider()
|
||||
fs, cleanup, err := wiretest.InitializeKustomization()
|
||||
|
|
@ -43,6 +54,36 @@ func TestPrune(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(fs), 2)
|
||||
|
||||
// run the prune
|
||||
pruneObject, err := kp.GetPruneConfig(fs[1])
|
||||
assert.NoError(t, err)
|
||||
p, donep, err := wiretest.InitializePrune(pruneObject, &object.Commit{}, buf)
|
||||
defer donep()
|
||||
assert.NoError(t, err)
|
||||
pr, err := p.Do()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(pr.Resources), 0)
|
||||
}
|
||||
|
||||
/* TestPruneOneObject take following steps
|
||||
1. create a Kustomization with a ConfigMapGenerator and an inventory object
|
||||
2. apply the kustomization
|
||||
3. update the ConfigMapGenerator so that the ConfigMap that gets generated has a different name
|
||||
4. apply the kustomization again
|
||||
5. confirm that there are 3 ConfigMaps (including the inventroy ConfigMap)
|
||||
6. run prune
|
||||
7. confirm that there are 2 ConfigMaps (the second ConfigMap and the inventory object)
|
||||
*/
|
||||
func TestPruneOneObject(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
kp := wiretest.InitializConfigProvider()
|
||||
fs, cleanup, err := wiretest.InitializeKustomization()
|
||||
assert.NoError(t, err)
|
||||
defer cleanup()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(fs), 2)
|
||||
|
||||
// call apply to create the first configmap
|
||||
objects, err := kp.GetConfig(fs[0])
|
||||
assert.NoError(t, err)
|
||||
a, donea, err := wiretest.InitializeApply(objects, &object.Commit{}, buf)
|
||||
|
|
@ -50,11 +91,24 @@ func TestPrune(t *testing.T) {
|
|||
defer donea()
|
||||
_, err = a.Do()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// call apply again to create the second configmap
|
||||
a.Resources, err = kp.GetConfig(fs[1])
|
||||
assert.NoError(t, err)
|
||||
_, err = a.Do()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// confirm that there are three ConfigMaps
|
||||
cmList := &unstructured.UnstructuredList{}
|
||||
cmList.SetGroupVersionKind(schema.GroupVersionKind{
|
||||
Kind: "ConfigMapList",
|
||||
Version: "v1",
|
||||
})
|
||||
err = a.DynamicClient.List(context.Background(), cmList, "default", nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(cmList.Items), 3)
|
||||
|
||||
// run the prune
|
||||
pruneObject, err := kp.GetPruneConfig(fs[1])
|
||||
assert.NoError(t, err)
|
||||
p, donep, err := wiretest.InitializePrune(pruneObject, &object.Commit{}, buf)
|
||||
|
|
@ -64,4 +118,492 @@ func TestPrune(t *testing.T) {
|
|||
pr, err := p.Do()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(pr.Resources), 1)
|
||||
|
||||
// confirm that one ConfigMap is deleted.
|
||||
// There are two ConfigMap left.
|
||||
err = a.DynamicClient.List(context.Background(), cmList, "default", nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(cmList.Items), 2)
|
||||
}
|
||||
|
||||
func setupKustomizeWithDeployment(s string) (string, error) {
|
||||
f, err := ioutil.TempDir("/tmp", "TestApply")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = ioutil.WriteFile(filepath.Join(f, "kustomization.yaml"), []byte(`apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
`+s+
|
||||
`
|
||||
resources:
|
||||
- deployment.yaml
|
||||
|
||||
inventory:
|
||||
type: ConfigMap
|
||||
configMap:
|
||||
name: inventory
|
||||
namespace: default
|
||||
|
||||
namespace: default
|
||||
`), 0644)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(filepath.Join(f, "deployment.yaml"), []byte(`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: mysql
|
||||
labels:
|
||||
app: mysql
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: mysql
|
||||
strategy:
|
||||
type: Recreate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: mysql
|
||||
spec:
|
||||
containers:
|
||||
- image: mysql:5.6
|
||||
name: mysql
|
||||
env:
|
||||
- name: MYSQL_ROOT_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: pass
|
||||
key: password
|
||||
`), 0644)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return f, nil
|
||||
}
|
||||
|
||||
/* TestPruneConfigMapWithDeployment take following steps
|
||||
1. create a Kustomization with a SecretGenerator, a Deployment
|
||||
that refers to the generated Secret as well as an inventory object
|
||||
2. apply the kustomization
|
||||
3. update the SecretGenerator so that the Secret that gets generated
|
||||
has a different name
|
||||
4. apply the kustomization again
|
||||
5. confirm that there are 2 Secrets
|
||||
6. run prune
|
||||
7. confirm that there are 2 Secrets, the first generated Secret is
|
||||
not deleted since it is referred by the Deployment and the
|
||||
Deployment object is not removed yet.
|
||||
*/
|
||||
func TestPruneConfigMapWithDeployment(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
kp := wiretest.InitializConfigProvider()
|
||||
|
||||
// setup the first version of resource configurations
|
||||
// and run apply
|
||||
f1, err := setupKustomizeWithDeployment(`
|
||||
secretGenerator:
|
||||
- name: pass
|
||||
literals:
|
||||
- password=secret
|
||||
`)
|
||||
assert.NoError(t, err)
|
||||
defer os.RemoveAll(f1)
|
||||
assert.NoError(t, err)
|
||||
objects, err := kp.GetConfig(f1)
|
||||
assert.NoError(t, err)
|
||||
a, donea, err := wiretest.InitializeApply(objects, &object.Commit{}, buf)
|
||||
assert.NoError(t, err)
|
||||
defer donea()
|
||||
_, err = a.Do()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// setup the second version of resource configurations
|
||||
// and run apply
|
||||
f2, err := setupKustomizeWithDeployment(`
|
||||
secretGenerator:
|
||||
- name: pass
|
||||
literals:
|
||||
- password=secret
|
||||
- a=b
|
||||
`)
|
||||
assert.NoError(t, err)
|
||||
defer os.RemoveAll(f2)
|
||||
a.Resources, err = kp.GetConfig(f2)
|
||||
assert.NoError(t, err)
|
||||
_, err = a.Do()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Confirm that there are two Secrets
|
||||
sList := &unstructured.UnstructuredList{}
|
||||
sList.SetGroupVersionKind(schema.GroupVersionKind{
|
||||
Kind: "SecretList",
|
||||
Version: "v1",
|
||||
})
|
||||
err = a.DynamicClient.List(context.Background(), sList, "default", nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(sList.Items), 2)
|
||||
|
||||
// Run prune and assert there are no objects get deleted
|
||||
pruneObject, err := kp.GetPruneConfig(f2)
|
||||
assert.NoError(t, err)
|
||||
p, donep, err := wiretest.InitializePrune(pruneObject, &object.Commit{}, buf)
|
||||
defer donep()
|
||||
assert.NoError(t, err)
|
||||
p.DynamicClient = a.DynamicClient
|
||||
pr, err := p.Do()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(pr.Resources), 0)
|
||||
|
||||
// Confirm that there are two Secrets
|
||||
err = a.DynamicClient.List(context.Background(), sList, "default", nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(sList.Items), 2)
|
||||
}
|
||||
|
||||
func setupKustomizeWithStatefulSet(s string) (string, error) {
|
||||
f, err := ioutil.TempDir("/tmp", "TestApply")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = ioutil.WriteFile(filepath.Join(f, "kustomization.yaml"), []byte(`apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
`+s+
|
||||
`
|
||||
resources:
|
||||
- statefulset.yaml
|
||||
|
||||
inventory:
|
||||
type: ConfigMap
|
||||
configMap:
|
||||
name: inventory
|
||||
namespace: default
|
||||
|
||||
namespace: default
|
||||
`), 0644)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(filepath.Join(f, "statefulset.yaml"), []byte(`
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: web
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: nginx
|
||||
serviceName: "nginx"
|
||||
replicas: 3 # by default is 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
terminationGracePeriodSeconds: 10
|
||||
containers:
|
||||
- name: nginx
|
||||
image: k8s.gcr.io/nginx-slim:0.8
|
||||
ports:
|
||||
- containerPort: 80
|
||||
name: web
|
||||
env:
|
||||
- name: PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: pass
|
||||
key: password
|
||||
`), 0644)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return f, nil
|
||||
}
|
||||
|
||||
/* TestPruneConfigMapWithStatefulSet take following steps
|
||||
1. create a Kustomization with a SecretGenerator, a StatefulSet
|
||||
that refers to the generated Secret as well as an inventory object
|
||||
2. apply the kustomization
|
||||
3. update the SecretGenerator so that the Secret that gets generated
|
||||
has a different name
|
||||
4. apply the kustomization again
|
||||
5. confirm that there are 2 Secrets
|
||||
6. run prune
|
||||
7. confirm that there are 2 Secrets, the first generated Secret is
|
||||
not deleted since it is referred by the StatefulSet and the
|
||||
Deployment object is not removed yet.
|
||||
*/
|
||||
func TestPruneConfigMapWithStatefulSet(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
kp := wiretest.InitializConfigProvider()
|
||||
|
||||
// setup the first version of resource configurations
|
||||
// and run apply
|
||||
f1, err := setupKustomizeWithStatefulSet(`
|
||||
secretGenerator:
|
||||
- name: pass
|
||||
literals:
|
||||
- password=secret
|
||||
`)
|
||||
assert.NoError(t, err)
|
||||
defer os.RemoveAll(f1)
|
||||
assert.NoError(t, err)
|
||||
objects, err := kp.GetConfig(f1)
|
||||
assert.NoError(t, err)
|
||||
a, donea, err := wiretest.InitializeApply(objects, &object.Commit{}, buf)
|
||||
assert.NoError(t, err)
|
||||
defer donea()
|
||||
_, err = a.Do()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// setup the second version of resource configurations
|
||||
// and run apply
|
||||
f2, err := setupKustomizeWithStatefulSet(`
|
||||
secretGenerator:
|
||||
- name: pass
|
||||
literals:
|
||||
- password=secret
|
||||
- a=b
|
||||
`)
|
||||
assert.NoError(t, err)
|
||||
defer os.RemoveAll(f2)
|
||||
a.Resources, err = kp.GetConfig(f2)
|
||||
assert.NoError(t, err)
|
||||
_, err = a.Do()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Confirm that there are two Secrets
|
||||
sList := &unstructured.UnstructuredList{}
|
||||
sList.SetGroupVersionKind(schema.GroupVersionKind{
|
||||
Kind: "SecretList",
|
||||
Version: "v1",
|
||||
})
|
||||
err = a.DynamicClient.List(context.Background(), sList, "default", nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(sList.Items), 2)
|
||||
|
||||
// Run prune and assert there are no objects get deleted
|
||||
pruneObject, err := kp.GetPruneConfig(f2)
|
||||
assert.NoError(t, err)
|
||||
p, donep, err := wiretest.InitializePrune(pruneObject, &object.Commit{}, buf)
|
||||
defer donep()
|
||||
assert.NoError(t, err)
|
||||
p.DynamicClient = a.DynamicClient
|
||||
pr, err := p.Do()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(pr.Resources), 0)
|
||||
|
||||
// Confirm that there are two Secrets
|
||||
err = a.DynamicClient.List(context.Background(), sList, "default", nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(sList.Items), 2)
|
||||
}
|
||||
|
||||
func setupKustomizeWithMultipleObjects(s string) (string, error) {
|
||||
f, err := ioutil.TempDir("/tmp", "TestApply")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = ioutil.WriteFile(filepath.Join(f, "kustomization.yaml"), []byte(`apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
`+s+
|
||||
`
|
||||
resources:
|
||||
- deployment.yaml
|
||||
- service.yaml
|
||||
|
||||
inventory:
|
||||
type: ConfigMap
|
||||
configMap:
|
||||
name: inventory
|
||||
namespace: default
|
||||
|
||||
namespace: default
|
||||
`), 0644)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(filepath.Join(f, "deployment.yaml"), []byte(`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: mysql
|
||||
labels:
|
||||
app: mysql
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: mysql
|
||||
strategy:
|
||||
type: Recreate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: mysql
|
||||
spec:
|
||||
containers:
|
||||
- image: mysql:5.6
|
||||
name: mysql
|
||||
env:
|
||||
- name: MYSQL_ROOT_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: pass
|
||||
key: password
|
||||
`), 0644)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(filepath.Join(f, "service.yaml"), []byte(`
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: mysql
|
||||
labels:
|
||||
app: mysql
|
||||
annotations: {}
|
||||
spec:
|
||||
ports:
|
||||
- port: 3306
|
||||
selector:
|
||||
app: mysql
|
||||
`), 0644)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return f, nil
|
||||
}
|
||||
|
||||
/* TestPruneConfigMapWithMultipleObjects take following steps
|
||||
1. create a Kustomization with
|
||||
a SecretGenerator
|
||||
a Deployment that uses the generated Secret
|
||||
a Service
|
||||
an inventory ConfigMap
|
||||
2. apply the kustomization
|
||||
3. update the SecretGenerator so that the Secret that gets generated
|
||||
has a different name
|
||||
3. add a namePrefix in the kustomization
|
||||
4. apply the kustomization again
|
||||
5. confirm that there are
|
||||
2 Secrets
|
||||
2 Deployments
|
||||
2 Services
|
||||
6. run prune and confirms 3 objects are deleted
|
||||
7. confirm that there are
|
||||
1 Secret
|
||||
1 Deployment
|
||||
1 Service
|
||||
*/
|
||||
func TestPruneConfigMapWithMultipleObjects(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
kp := wiretest.InitializConfigProvider()
|
||||
ctx := context.Background()
|
||||
|
||||
// setup the first version of resource configurations
|
||||
// and run apply
|
||||
f1, err := setupKustomizeWithMultipleObjects(`
|
||||
secretGenerator:
|
||||
- name: pass
|
||||
literals:
|
||||
- password=secret
|
||||
`)
|
||||
assert.NoError(t, err)
|
||||
defer os.RemoveAll(f1)
|
||||
assert.NoError(t, err)
|
||||
objects, err := kp.GetConfig(f1)
|
||||
assert.NoError(t, err)
|
||||
a, donea, err := wiretest.InitializeApply(objects, &object.Commit{}, buf)
|
||||
assert.NoError(t, err)
|
||||
defer donea()
|
||||
|
||||
svList := &unstructured.UnstructuredList{}
|
||||
svList.SetGroupVersionKind(schema.GroupVersionKind{
|
||||
Kind: "ServiceList",
|
||||
Version: "v1",
|
||||
})
|
||||
err = a.DynamicClient.List(ctx, svList, "default", nil)
|
||||
assert.NoError(t, err)
|
||||
serviceNumber := len(svList.Items)
|
||||
|
||||
_, err = a.Do()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// setup the second version of resource configurations
|
||||
// and run apply
|
||||
f2, err := setupKustomizeWithMultipleObjects(`
|
||||
secretGenerator:
|
||||
- name: pass
|
||||
literals:
|
||||
- password=secret
|
||||
- a=b
|
||||
|
||||
namePrefix: test-
|
||||
`)
|
||||
assert.NoError(t, err)
|
||||
defer os.RemoveAll(f2)
|
||||
a.Resources, err = kp.GetConfig(f2)
|
||||
assert.NoError(t, err)
|
||||
_, err = a.Do()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Confirm that there are two Secrets
|
||||
sList := &unstructured.UnstructuredList{}
|
||||
sList.SetGroupVersionKind(schema.GroupVersionKind{
|
||||
Kind: "SecretList",
|
||||
Version: "v1",
|
||||
})
|
||||
err = a.DynamicClient.List(ctx, sList, "default", nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(sList.Items), 2)
|
||||
|
||||
// Confirm that there are two Deployments
|
||||
dpList := &unstructured.UnstructuredList{}
|
||||
dpList.SetGroupVersionKind(schema.GroupVersionKind{
|
||||
Kind: "DeploymentList",
|
||||
Version: "v1",
|
||||
Group: "apps",
|
||||
})
|
||||
err = a.DynamicClient.List(ctx, dpList, "default", nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(dpList.Items), 2)
|
||||
|
||||
// Confirm that there are two Services
|
||||
err = a.DynamicClient.List(ctx, svList, "default", nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(svList.Items), serviceNumber+2)
|
||||
|
||||
// Run prune and assert there are 3 objects get deleted
|
||||
pruneObject, err := kp.GetPruneConfig(f2)
|
||||
assert.NoError(t, err)
|
||||
p, donep, err := wiretest.InitializePrune(pruneObject, &object.Commit{}, buf)
|
||||
defer donep()
|
||||
assert.NoError(t, err)
|
||||
p.DynamicClient = a.DynamicClient
|
||||
pr, err := p.Do()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(pr.Resources), 3)
|
||||
|
||||
// Confirm that there are one Secret
|
||||
err = a.DynamicClient.List(ctx, sList, "default", nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(sList.Items), 1)
|
||||
|
||||
// Confirm that there are one Deployment
|
||||
err = a.DynamicClient.List(ctx, dpList, "default", nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(dpList.Items), 1)
|
||||
|
||||
// Confirm that there are one Service
|
||||
err = a.DynamicClient.List(ctx, svList, "default", nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(svList.Items), serviceNumber+1)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ import (
|
|||
"sigs.k8s.io/cli-experimental/internal/pkg/wirecli/wirek8s"
|
||||
)
|
||||
|
||||
// TODO(Liujingfang1): split into per command wire
|
||||
|
||||
// ProviderSet defines dependencies for initializing objects
|
||||
var ProviderSet = wire.NewSet(
|
||||
wirek8s.ProviderSet,
|
||||
|
|
|
|||
Loading…
Reference in New Issue