address comments and add tests for pruning

This commit is contained in:
Jingfang Liu 2019-05-13 16:23:58 -07:00 committed by jingfangliu
parent 68698efcbe
commit f3ca02a96a
15 changed files with 805 additions and 49 deletions

28
Dockerfile Normal file
View File

@ -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"]

View File

@ -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 {

View File

@ -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)

View File

@ -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 {

View File

@ -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
View File

@ -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
View File

@ -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=

View File

@ -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 {

View File

@ -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())

View File

@ -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 {

View File

@ -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)
}

View File

@ -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.
*/

View File

@ -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)

View File

@ -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)
}

View File

@ -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,