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,7 +30,17 @@ func GetApplyCommand(a util.Args) *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "apply",
|
Use: "apply",
|
||||||
Short: "Apply resource configurations.",
|
Short: "Apply resource configurations.",
|
||||||
Long: `Apply resource configurations to k8s cluster.`,
|
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),
|
Args: cobra.MinimumNArgs(1),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,9 +28,11 @@ import (
|
||||||
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
||||||
func Execute(args []string, fn func(*cobra.Command)) error {
|
func Execute(args []string, fn func(*cobra.Command)) error {
|
||||||
rootCmd := &cobra.Command{
|
rootCmd := &cobra.Command{
|
||||||
Use: "k2",
|
// TODO(Liujingfang1): change this binary to a better name
|
||||||
|
Use: "cli-experimental",
|
||||||
Short: "kubectl version 2",
|
Short: "kubectl version 2",
|
||||||
Long: `kubectl version 2`,
|
Long: `kubectl version 2
|
||||||
|
with commands apply, prune, delete and dynamic commands`,
|
||||||
}
|
}
|
||||||
if fn != nil {
|
if fn != nil {
|
||||||
fn(rootCmd)
|
fn(rootCmd)
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,15 @@ import (
|
||||||
func GetDeleteCommand(a util.Args) *cobra.Command {
|
func GetDeleteCommand(a util.Args) *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "delete",
|
Use: "delete",
|
||||||
Short: "Delete resources from a K8s cluster.",
|
Short: "Delete resources from a Kubernetes cluster.",
|
||||||
Long: `Delete resources from a K8s 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),
|
Args: cobra.MinimumNArgs(1),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,9 +28,49 @@ func GetPruneCommand(a util.Args) *cobra.Command {
|
||||||
Use: "prune",
|
Use: "prune",
|
||||||
Short: "Prune obsolete resources.",
|
Short: "Prune obsolete resources.",
|
||||||
Long: `Prune obsolete resources.
|
Long: `Prune obsolete resources.
|
||||||
It is based on checking the inventory annotation that
|
# Prune the configurations from a directory containing kustomization.yaml -e.g. dir/kustomization.yaml
|
||||||
is stored in the resource configuration that is passed
|
k2 prune dir
|
||||||
to prune command.`,
|
|
||||||
|
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),
|
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/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc // indirect
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.8.5 // indirect
|
github.com/grpc-ecosystem/grpc-gateway v1.8.5 // indirect
|
||||||
github.com/imdario/mergo v0.3.7 // 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/jgautheron/goconst v0.0.0-20170703170152-9740945f5dcb // indirect
|
||||||
github.com/json-iterator/go v1.1.6 // indirect
|
github.com/json-iterator/go v1.1.6 // indirect
|
||||||
github.com/mdempsky/maligned v0.0.0-20180708014732-6e39bd26a8c8 // 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 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
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/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/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 h1:LzQXZOgg4CQfE6bFvXGM30YZL1WW/M337pXml+GrcZ4=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
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/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 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
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 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI=
|
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/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 h1:D5s1HIu80AcMGcqmk7fNIVptmAubVHHaj3v5Upex6Zs=
|
||||||
github.com/jgautheron/goconst v0.0.0-20170703170152-9740945f5dcb/go.mod h1:82TxjOpWQiPmywlbIaB2ZkqJoSYJdLGPgAJDvM3PbKc=
|
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 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs=
|
||||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
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/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 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
|
||||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
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 h1:2gxZ0XQIU/5z3Z3bUBu+FXuk2pFbkN6tcwi/pjyaDic=
|
||||||
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
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=
|
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.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 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
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 h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
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=
|
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/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 h1:U4t4R6YkofJ5xHm3dJzuRpPZ0mr5MMCoAWooScCR7aA=
|
||||||
github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo=
|
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 h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
|
||||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
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=
|
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 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
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/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.2/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||||
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
|
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
|
||||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
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.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
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 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4=
|
||||||
github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
|
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=
|
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/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 h1:Adglfbi5p9Z0BmK2oKU9nTG+zKfniSfnaMYB+ULd+Ro=
|
||||||
github.com/xanzy/ssh-agent v0.2.0/go.mod h1:0NyE30eGUDliuLEHJgYte/zncp2zdTStcOnWhgSqHD8=
|
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.1/go.mod h1:gug0GbSHa8Pafr0d2urOSgoXHZ6x/RUlaiT0d9pqb4A=
|
||||||
go.opencensus.io v0.19.2 h1:ZZpq6xI6kv/LuE/5s5UQvBU5vMjvRnPb8PvJrIntAnc=
|
go.opencensus.io v0.19.2 h1:ZZpq6xI6kv/LuE/5s5UQvBU5vMjvRnPb8PvJrIntAnc=
|
||||||
go.opencensus.io v0.19.2/go.mod h1:NO/8qkisMZLZ1FCsKNqtJPwc8/TaclWyY0B6wcYNg9M=
|
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 h1:XCJQEf3W6eZaVwhRBof6ImoYGJSITeKWsyeh3HFu/5o=
|
||||||
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
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-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-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 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
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-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-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-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-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-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
|
||||||
|
|
|
||||||
|
|
@ -31,9 +31,17 @@ import (
|
||||||
|
|
||||||
// Apply applies directories
|
// Apply applies directories
|
||||||
type Apply struct {
|
type Apply struct {
|
||||||
|
// DynamicClient is the client used to talk
|
||||||
|
// with the cluster
|
||||||
DynamicClient client.Client
|
DynamicClient client.Client
|
||||||
|
|
||||||
|
// Out stores the output
|
||||||
Out io.Writer
|
Out io.Writer
|
||||||
|
|
||||||
|
// Resources is a list of resource configurations
|
||||||
Resources clik8s.ResourceConfigs
|
Resources clik8s.ResourceConfigs
|
||||||
|
|
||||||
|
// Commit is a git commit object
|
||||||
Commit *object.Commit
|
Commit *object.Commit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -49,7 +57,7 @@ func (a *Apply) Do() (Result, error) {
|
||||||
// TODO(Liuijngfang1): add a dry-run for all objects
|
// TODO(Liuijngfang1): add a dry-run for all objects
|
||||||
// When the dry-run passes, proceed to the actual apply
|
// 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()
|
annotation := u.GetAnnotations()
|
||||||
_, ok := annotation[inventory.InventoryAnnotation]
|
_, ok := annotation[inventory.InventoryAnnotation]
|
||||||
|
|
||||||
|
|
@ -113,8 +121,8 @@ func mergeInventoryAnnotation(newObj, oldObj *unstructured.Unstructured) (*unstr
|
||||||
return newObj, nil
|
return newObj, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// adjustOrder moves the inventory object to be the first resource
|
// normalizeResourceOrdering moves the inventory object to be the first resource
|
||||||
func adjustOrder(resources clik8s.ResourceConfigs) []*unstructured.Unstructured {
|
func normalizeResourceOrdering(resources clik8s.ResourceConfigs) []*unstructured.Unstructured {
|
||||||
var results []*unstructured.Unstructured
|
var results []*unstructured.Unstructured
|
||||||
index := -1
|
index := -1
|
||||||
for i, u := range resources {
|
for i, u := range resources {
|
||||||
|
|
|
||||||
|
|
@ -334,7 +334,10 @@ var _ = Describe("Client", func() {
|
||||||
By("encoding the deployment as unstructured")
|
By("encoding the deployment as unstructured")
|
||||||
u := &unstructured.Unstructured{}
|
u := &unstructured.Unstructured{}
|
||||||
scheme.Convert(dep, u, nil)
|
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())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
u.SetGroupVersionKind(schema.GroupVersionKind{
|
u.SetGroupVersionKind(schema.GroupVersionKind{
|
||||||
Group: "apps",
|
Group: "apps",
|
||||||
|
|
@ -349,7 +352,8 @@ var _ = Describe("Client", func() {
|
||||||
actual, err := clientset.AppsV1().Deployments(ns).Get(dep.Name, metav1.GetOptions{})
|
actual, err := clientset.AppsV1().Deployments(ns).Get(dep.Name, metav1.GetOptions{})
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(actual).NotTo(BeNil())
|
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)
|
close(done)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -379,7 +383,7 @@ var _ = Describe("Client", func() {
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
})
|
})
|
||||||
u.SetAnnotations(map[string]string{"foo": "bar"})
|
u.SetAnnotations(map[string]string{"foo": "bar"})
|
||||||
annotation, err := patch.GetModifiedConfiguration(u, false)
|
annotation, err := patch.SerializeLastApplied(u, false)
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
err = cl.Apply(context.TODO(), u)
|
err = cl.Apply(context.TODO(), u)
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
|
||||||
|
|
@ -31,9 +31,17 @@ import (
|
||||||
|
|
||||||
// Delete applies directories
|
// Delete applies directories
|
||||||
type Delete struct {
|
type Delete struct {
|
||||||
|
// DynamicClient is the client used to talk
|
||||||
|
// with the cluster
|
||||||
DynamicClient client.Client
|
DynamicClient client.Client
|
||||||
|
|
||||||
|
// Out stores the output
|
||||||
Out io.Writer
|
Out io.Writer
|
||||||
|
|
||||||
|
// Resources is a list of resource configurations
|
||||||
Resources clik8s.ResourceConfigs
|
Resources clik8s.ResourceConfigs
|
||||||
|
|
||||||
|
// Commit is a git commit object
|
||||||
Commit *object.Commit
|
Commit *object.Commit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,18 +53,19 @@ type Result struct {
|
||||||
// Do executes the delete
|
// Do executes the delete
|
||||||
func (a *Delete) Do() (Result, error) {
|
func (a *Delete) Do() (Result, error) {
|
||||||
fmt.Fprintf(a.Out, "Doing `cli-experimental delete`\n")
|
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()
|
annotations := u.GetAnnotations()
|
||||||
_, ok := annotations[inventory.InventoryAnnotation]
|
_, ok := annotations[inventory.InventoryAnnotation]
|
||||||
if ok {
|
if ok {
|
||||||
err := a.deleteLeftOvers(annotations)
|
err := a.handleInventroy(ctx, annotations)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "failed to delete leftovers for inventory %v\n", err)
|
fmt.Fprintf(os.Stderr, "failed to delete leftovers for inventory %v\n", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err := a.deleteObject(u.GroupVersionKind(), u.GetNamespace(), u.GetName())
|
err := a.deleteObject(ctx, u.GroupVersionKind(), u.GetNamespace(), u.GetName())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprint(os.Stderr, err)
|
fmt.Fprint(os.Stderr, err)
|
||||||
}
|
}
|
||||||
|
|
@ -65,7 +74,12 @@ func (a *Delete) Do() (Result, error) {
|
||||||
return Result{Resources: a.Resources}, nil
|
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()
|
inv := inventory.NewInventory()
|
||||||
err := inv.LoadFromAnnotation(annotations)
|
err := inv.LoadFromAnnotation(annotations)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -80,7 +94,7 @@ func (a *Delete) deleteLeftOvers(annotations map[string]string) error {
|
||||||
Version: id.Version,
|
Version: id.Version,
|
||||||
Kind: id.Kind,
|
Kind: id.Kind,
|
||||||
}
|
}
|
||||||
err = a.deleteObject(gvk, id.Namespace, id.Name)
|
err = a.deleteObject(ctx, gvk, id.Namespace, id.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprint(os.Stderr, err)
|
fmt.Fprint(os.Stderr, err)
|
||||||
}
|
}
|
||||||
|
|
@ -88,13 +102,13 @@ func (a *Delete) deleteLeftOvers(annotations map[string]string) error {
|
||||||
return nil
|
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 := &unstructured.Unstructured{}
|
||||||
obj.SetGroupVersionKind(gvk)
|
obj.SetGroupVersionKind(gvk)
|
||||||
obj.SetNamespace(ns)
|
obj.SetNamespace(ns)
|
||||||
obj.SetName(nm)
|
obj.SetName(nm)
|
||||||
|
|
||||||
err := a.DynamicClient.Delete(context.Background(), obj, &metav1.DeleteOptions{})
|
err := a.DynamicClient.Delete(ctx, obj, &metav1.DeleteOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.IsNotFound(err) {
|
if errors.IsNotFound(err) {
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -104,8 +118,9 @@ func (a *Delete) deleteObject(gvk schema.GroupVersionKind, ns, nm string) error
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// adjustOrder move the inventory object to be the last resource
|
// normalizeResourceOrdering move the inventory object to be the last resource
|
||||||
func adjustOrder(resources clik8s.ResourceConfigs) []*unstructured.Unstructured {
|
// 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
|
var results []*unstructured.Unstructured
|
||||||
index := -1
|
index := -1
|
||||||
for i, u := range resources {
|
for i, u := range resources {
|
||||||
|
|
|
||||||
|
|
@ -18,12 +18,10 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"gopkg.in/src-d/go-git.v4/plumbing/object"
|
"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/clik8s"
|
||||||
"sigs.k8s.io/cli-experimental/internal/pkg/delete"
|
"sigs.k8s.io/cli-experimental/internal/pkg/delete"
|
||||||
"sigs.k8s.io/cli-experimental/internal/pkg/wirecli/wiretest"
|
"sigs.k8s.io/cli-experimental/internal/pkg/wirecli/wiretest"
|
||||||
|
|
@ -39,7 +37,7 @@ func TestDeleteEmpty(t *testing.T) {
|
||||||
assert.Equal(t, delete.Result{}, r)
|
assert.Equal(t, delete.Result{}, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPrune(t *testing.T) {
|
func TestDelete(t *testing.T) {
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
kp := wiretest.InitializConfigProvider()
|
kp := wiretest.InitializConfigProvider()
|
||||||
fs, cleanup, err := wiretest.InitializeKustomization()
|
fs, cleanup, err := wiretest.InitializeKustomization()
|
||||||
|
|
@ -65,7 +63,7 @@ func TestPrune(t *testing.T) {
|
||||||
Kind: "ConfigMapList",
|
Kind: "ConfigMapList",
|
||||||
Version: "v1",
|
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.NoError(t, err)
|
||||||
assert.Equal(t, len(cmList.Items), 3)
|
assert.Equal(t, len(cmList.Items), 3)
|
||||||
|
|
||||||
|
|
@ -76,7 +74,7 @@ func TestPrune(t *testing.T) {
|
||||||
_, err = d.Do()
|
_, err = d.Do()
|
||||||
assert.NoError(t, err)
|
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.NoError(t, err)
|
||||||
assert.Equal(t, len(cmList.Items), 0)
|
assert.Equal(t, len(cmList.Items), 0)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,3 +12,79 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package prune
|
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,11 +31,21 @@ import (
|
||||||
"sigs.k8s.io/kustomize/pkg/inventory"
|
"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 {
|
type Prune struct {
|
||||||
|
// DynamicClient is the client used to talk
|
||||||
|
// with the cluster
|
||||||
DynamicClient client.Client
|
DynamicClient client.Client
|
||||||
|
|
||||||
|
// Out stores the output
|
||||||
Out io.Writer
|
Out io.Writer
|
||||||
|
|
||||||
|
// Resources is the resource used for pruning
|
||||||
Resources clik8s.ResourcePruneConfigs
|
Resources clik8s.ResourcePruneConfigs
|
||||||
|
|
||||||
|
// Commit is a git commit object
|
||||||
Commit *object.Commit
|
Commit *object.Commit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -50,6 +60,7 @@ func (o *Prune) Do() (Result, error) {
|
||||||
return Result{}, nil
|
return Result{}, nil
|
||||||
}
|
}
|
||||||
fmt.Fprintf(o.Out, "Doing `cli-experimental prune`\n")
|
fmt.Fprintf(o.Out, "Doing `cli-experimental prune`\n")
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
u := (*unstructured.Unstructured)(o.Resources)
|
u := (*unstructured.Unstructured)(o.Resources)
|
||||||
annotation := u.GetAnnotations()
|
annotation := u.GetAnnotations()
|
||||||
|
|
@ -59,7 +70,7 @@ func (o *Prune) Do() (Result, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
obj := u.DeepCopy()
|
obj := u.DeepCopy()
|
||||||
err := o.DynamicClient.Get(context.Background(),
|
err := o.DynamicClient.Get(ctx,
|
||||||
types.NamespacedName{Namespace: u.GetNamespace(), Name: u.GetName()}, obj)
|
types.NamespacedName{Namespace: u.GetNamespace(), Name: u.GetName()}, obj)
|
||||||
|
|
||||||
if err != nil {
|
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)
|
fmt.Fprintf(os.Stderr, "retrieving current configuration of %s from server for %v", u.GetName(), err)
|
||||||
return Result{}, err
|
return Result{}, err
|
||||||
}
|
}
|
||||||
obj, results, err := o.runPrune(obj)
|
obj, results, err := o.runPrune(ctx, obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Result{}, err
|
return Result{}, err
|
||||||
}
|
}
|
||||||
|
|
@ -90,7 +101,7 @@ func (o *Prune) Do() (Result, error) {
|
||||||
// https://github.com/kubernetes-sigs/kustomize/tree/master/pkg/inventory
|
// https://github.com/kubernetes-sigs/kustomize/tree/master/pkg/inventory
|
||||||
// This is based on the KEP
|
// This is based on the KEP
|
||||||
// https://github.com/kubernetes/enhancements/pull/810
|
// 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) {
|
*unstructured.Unstructured, []*unstructured.Unstructured, error) {
|
||||||
var results []*unstructured.Unstructured
|
var results []*unstructured.Unstructured
|
||||||
annotations := obj.GetAnnotations()
|
annotations := obj.GetAnnotations()
|
||||||
|
|
@ -103,7 +114,7 @@ func (o *Prune) runPrune(obj *unstructured.Unstructured) (
|
||||||
Version: item.Version,
|
Version: item.Version,
|
||||||
Kind: item.Kind,
|
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 {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -116,7 +127,8 @@ func (o *Prune) runPrune(obj *unstructured.Unstructured) (
|
||||||
return obj, results, nil
|
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 := &unstructured.Unstructured{}
|
||||||
obj.SetGroupVersionKind(gvk)
|
obj.SetGroupVersionKind(gvk)
|
||||||
obj.SetNamespace(ns)
|
obj.SetNamespace(ns)
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,16 @@ package prune_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"gopkg.in/src-d/go-git.v4/plumbing/object"
|
"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/clik8s"
|
||||||
"sigs.k8s.io/cli-experimental/internal/pkg/prune"
|
"sigs.k8s.io/cli-experimental/internal/pkg/prune"
|
||||||
"sigs.k8s.io/cli-experimental/internal/pkg/wirecli/wiretest"
|
"sigs.k8s.io/cli-experimental/internal/pkg/wirecli/wiretest"
|
||||||
|
|
@ -34,7 +40,12 @@ func TestPruneEmpty(t *testing.T) {
|
||||||
assert.Equal(t, prune.Result{}, r)
|
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)
|
buf := new(bytes.Buffer)
|
||||||
kp := wiretest.InitializConfigProvider()
|
kp := wiretest.InitializConfigProvider()
|
||||||
fs, cleanup, err := wiretest.InitializeKustomization()
|
fs, cleanup, err := wiretest.InitializeKustomization()
|
||||||
|
|
@ -43,6 +54,36 @@ func TestPrune(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, len(fs), 2)
|
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])
|
objects, err := kp.GetConfig(fs[0])
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
a, donea, err := wiretest.InitializeApply(objects, &object.Commit{}, buf)
|
a, donea, err := wiretest.InitializeApply(objects, &object.Commit{}, buf)
|
||||||
|
|
@ -50,11 +91,24 @@ func TestPrune(t *testing.T) {
|
||||||
defer donea()
|
defer donea()
|
||||||
_, err = a.Do()
|
_, err = a.Do()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// call apply again to create the second configmap
|
||||||
a.Resources, err = kp.GetConfig(fs[1])
|
a.Resources, err = kp.GetConfig(fs[1])
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
_, err = a.Do()
|
_, err = a.Do()
|
||||||
assert.NoError(t, err)
|
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])
|
pruneObject, err := kp.GetPruneConfig(fs[1])
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
p, donep, err := wiretest.InitializePrune(pruneObject, &object.Commit{}, buf)
|
p, donep, err := wiretest.InitializePrune(pruneObject, &object.Commit{}, buf)
|
||||||
|
|
@ -64,4 +118,492 @@ func TestPrune(t *testing.T) {
|
||||||
pr, err := p.Do()
|
pr, err := p.Do()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, len(pr.Resources), 1)
|
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"
|
"sigs.k8s.io/cli-experimental/internal/pkg/wirecli/wirek8s"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO(Liujingfang1): split into per command wire
|
||||||
|
|
||||||
// ProviderSet defines dependencies for initializing objects
|
// ProviderSet defines dependencies for initializing objects
|
||||||
var ProviderSet = wire.NewSet(
|
var ProviderSet = wire.NewSet(
|
||||||
wirek8s.ProviderSet,
|
wirek8s.ProviderSet,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue