diff --git a/cmd/kubectl/go.mod b/cmd/kubectl/go.mod index aee17a388..8bdfa40ff 100644 --- a/cmd/kubectl/go.mod +++ b/cmd/kubectl/go.mod @@ -4,7 +4,6 @@ go 1.13 require ( github.com/spf13/cobra v0.0.5 - k8s.io/api v0.17.0 k8s.io/apimachinery v0.17.0 k8s.io/cli-runtime v0.17.0 k8s.io/client-go v0.17.0 diff --git a/cmd/kubectl/kubectlcobra/grouping.go b/cmd/kubectl/kubectlcobra/grouping.go index 568682093..c7bd92fc1 100644 --- a/cmd/kubectl/kubectlcobra/grouping.go +++ b/cmd/kubectl/kubectlcobra/grouping.go @@ -5,7 +5,10 @@ package kubectlcobra import ( + "fmt" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/cli-runtime/pkg/resource" ) @@ -57,3 +60,86 @@ func sortGroupingObject(infos []*resource.Info) bool { } return false } + +// Adds the inventory of all objects (passed as infos) to the +// grouping object. Returns an error if a grouping object does not +// exist, or we are unable to successfully add the inventory to +// the grouping object; nil otherwise. Each object is in +// unstructured.Unstructured format. +func addInventoryToGroupingObj(infos []*resource.Info) error { + + // Iterate through the objects (infos), creating an Inventory struct + // as metadata for the object, or if it's the grouping object, store it. + var groupingObj *unstructured.Unstructured + inventoryMap := map[string]string{} + for _, info := range infos { + obj := info.Object + if isGroupingObject(obj) { + // If we have more than one grouping object--error. + if groupingObj != nil { + return fmt.Errorf("Error--applying more than one grouping object.") + } + var ok bool + groupingObj, ok = obj.(*unstructured.Unstructured) + if !ok { + return fmt.Errorf("Grouping object is not an Unstructured: %#v", groupingObj) + } + } else { + if obj == nil { + return fmt.Errorf("Creating inventory; object is nil") + } + gk := obj.GetObjectKind().GroupVersionKind().GroupKind() + inventory, err := createInventory(info.Namespace, info.Name, gk) + if err != nil { + return err + } + inventoryMap[inventory.String()] = "" + } + } + + // If we've found the grouping object, store the object metadata inventory + // in the grouping config map. + if groupingObj == nil { + return fmt.Errorf("Grouping object not found") + } + err := unstructured.SetNestedStringMap(groupingObj.UnstructuredContent(), inventoryMap, "data") + if err != nil { + return err + } + + return nil +} + +// retrieveInventoryFromGroupingObj returns a slice of pointers to the +// inventory metadata. This function finds the grouping object, then +// parses the stored resource metadata into Inventory structs. Returns +// an error if there is a problem parsing the data into Inventory +// structs, or if the grouping object is not in Unstructured format; nil +// otherwise. If a grouping object does not exist, or it does not have a +// "data" map, then returns an empty slice and no error. +func retrieveInventoryFromGroupingObj(infos []*resource.Info) ([]*Inventory, error) { + inventory := []*Inventory{} + groupingInfo, exists := findGroupingObject(infos) + if exists { + groupingObj, ok := groupingInfo.Object.(*unstructured.Unstructured) + if !ok { + err := fmt.Errorf("Grouping object is not an Unstructured: %#v", groupingObj) + return inventory, err + } + invMap, exists, err := unstructured.NestedStringMap(groupingObj.Object, "data") + if err != nil { + err := fmt.Errorf("Error retrieving inventory from grouping object.") + return inventory, err + } + if exists { + for invStr := range invMap { + inv, err := parseInventory(invStr) + if err != nil { + return inventory, err + } + inventory = append(inventory, inv) + } + } + } + return inventory, nil +} diff --git a/cmd/kubectl/kubectlcobra/grouping_test.go b/cmd/kubectl/kubectlcobra/grouping_test.go index 12911cf52..2aa957072 100644 --- a/cmd/kubectl/kubectlcobra/grouping_test.go +++ b/cmd/kubectl/kubectlcobra/grouping_test.go @@ -7,9 +7,9 @@ package kubectlcobra import ( "testing" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/cli-runtime/pkg/resource" ) @@ -19,12 +19,16 @@ var pod1Name = "pod-1" var pod2Name = "pod-2" var pod3Name = "pod-3" -var groupingObj = corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: testNamespace, - Name: groupingObjName, - Labels: map[string]string{ - GroupingLabel: "true", +var groupingObj = unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": map[string]interface{}{ + "name": groupingObjName, + "namespace": testNamespace, + "labels": map[string]interface{}{ + GroupingLabel: "true", + }, }, }, } @@ -35,10 +39,14 @@ var groupingInfo = &resource.Info{ Object: &groupingObj, } -var pod1 = corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: testNamespace, - Name: pod1Name, +var pod1 = unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Pod", + "metadata": map[string]interface{}{ + "name": pod1Name, + "namespace": testNamespace, + }, }, } @@ -48,10 +56,14 @@ var pod1Info = &resource.Info{ Object: &pod1, } -var pod2 = corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: testNamespace, - Name: pod2Name, +var pod2 = unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Pod", + "metadata": map[string]interface{}{ + "name": pod2Name, + "namespace": testNamespace, + }, }, } @@ -61,10 +73,14 @@ var pod2Info = &resource.Info{ Object: &pod2, } -var pod3 = corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: testNamespace, - Name: pod3Name, +var pod3 = unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Pod", + "metadata": map[string]interface{}{ + "name": pod3Name, + "namespace": testNamespace, + }, }, } @@ -215,3 +231,190 @@ func TestSortGroupingObject(t *testing.T) { } } } + +func TestAddRetrieveInventoryToFromGroupingObject(t *testing.T) { + tests := []struct { + infos []*resource.Info + expected []*Inventory + isError bool + }{ + // No grouping object is an error. + { + infos: []*resource.Info{}, + isError: true, + }, + // No grouping object is an error. + { + infos: []*resource.Info{pod1Info, pod2Info}, + isError: true, + }, + // Grouping object without other objects is OK. + { + infos: []*resource.Info{groupingInfo}, + expected: []*Inventory{}, + isError: false, + }, + // More than one grouping object is an error. + { + infos: []*resource.Info{groupingInfo, groupingInfo}, + expected: []*Inventory{}, + isError: true, + }, + // More than one grouping object is an error. + { + infos: []*resource.Info{groupingInfo, pod1Info, groupingInfo}, + expected: []*Inventory{}, + isError: true, + }, + { + infos: []*resource.Info{groupingInfo, pod1Info}, + expected: []*Inventory{ + &Inventory{ + Namespace: testNamespace, + Name: pod1Name, + GroupKind: schema.GroupKind{ + Group: "", + Kind: "Pod", + }, + }, + }, + isError: false, + }, + { + infos: []*resource.Info{pod1Info, groupingInfo}, + expected: []*Inventory{ + &Inventory{ + Namespace: testNamespace, + Name: pod1Name, + GroupKind: schema.GroupKind{ + Group: "", + Kind: "Pod", + }, + }, + }, + isError: false, + }, + { + infos: []*resource.Info{pod1Info, pod2Info, groupingInfo, pod3Info}, + expected: []*Inventory{ + &Inventory{ + Namespace: testNamespace, + Name: pod1Name, + GroupKind: schema.GroupKind{ + Group: "", + Kind: "Pod", + }, + }, + &Inventory{ + Namespace: testNamespace, + Name: pod2Name, + GroupKind: schema.GroupKind{ + Group: "", + Kind: "Pod", + }, + }, + &Inventory{ + Namespace: testNamespace, + Name: pod3Name, + GroupKind: schema.GroupKind{ + Group: "", + Kind: "Pod", + }, + }, + }, + isError: false, + }, + { + infos: []*resource.Info{pod1Info, pod2Info, pod3Info, groupingInfo}, + expected: []*Inventory{ + &Inventory{ + Namespace: testNamespace, + Name: pod1Name, + GroupKind: schema.GroupKind{ + Group: "", + Kind: "Pod", + }, + }, + &Inventory{ + Namespace: testNamespace, + Name: pod2Name, + GroupKind: schema.GroupKind{ + Group: "", + Kind: "Pod", + }, + }, + &Inventory{ + Namespace: testNamespace, + Name: pod3Name, + GroupKind: schema.GroupKind{ + Group: "", + Kind: "Pod", + }, + }, + }, + isError: false, + }, + { + infos: []*resource.Info{groupingInfo, pod1Info, pod2Info, pod3Info}, + expected: []*Inventory{ + &Inventory{ + Namespace: testNamespace, + Name: pod1Name, + GroupKind: schema.GroupKind{ + Group: "", + Kind: "Pod", + }, + }, + &Inventory{ + Namespace: testNamespace, + Name: pod2Name, + GroupKind: schema.GroupKind{ + Group: "", + Kind: "Pod", + }, + }, + &Inventory{ + Namespace: testNamespace, + Name: pod3Name, + GroupKind: schema.GroupKind{ + Group: "", + Kind: "Pod", + }, + }, + }, + isError: false, + }, + } + + for _, test := range tests { + err := addInventoryToGroupingObj(test.infos) + if test.isError && err == nil { + t.Errorf("Should have produced an error, but returned none.") + } + if !test.isError { + if err != nil { + t.Errorf("Received error when expecting none (%s)\n", err) + } else { + retrieved, err := retrieveInventoryFromGroupingObj(test.infos) + if err != nil { + t.Errorf("Error retrieving inventory: %s\n", err) + } + if len(test.expected) != len(retrieved) { + t.Errorf("Expected inventory for %d resources, actual %d", + len(test.expected), len(retrieved)) + } + for _, expected := range test.expected { + found := false + for _, actual := range retrieved { + if expected.Equals(actual) { + found = true + } + } + if !found { + t.Errorf("Expected inventory (%s) not found", expected) + } + } + } + } + } +} diff --git a/cmd/resource/fixgomod.sh b/cmd/resource/fixgomod.sh new file mode 100755 index 000000000..fdf07ff61 --- /dev/null +++ b/cmd/resource/fixgomod.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# Copyright 2019 The Kubernetes Authors. +# SPDX-License-Identifier: Apache-2.0 + +set -e + +: "${kyaml_major?Need to source VERSIONS}" +: "${kyaml_minor?Need to source VERSIONS}" +: "${kyaml_patch?Need to source VERSIONS}" + +: "${kstatus_major?Need to source VERSIONS}" +: "${kstatus_minor?Need to source VERSIONS}" +: "${kstatus_patch?Need to source VERSIONS}" + + +go mod edit -dropreplace=sigs.k8s.io/kustomize/kyaml@v0.0.0 +go mod edit -require=sigs.k8s.io/kustomize/kyaml@v$kyaml_major.$kyaml_minor.$kyaml_patch + +go mod edit -dropreplace=sigs.k8s.io/kustomize/kstatus@v0.0.0 +go mod edit -require=sigs.k8s.io/kustomize/kstatus@v$kstatus_major.$kstatus_minor.$kstatus_patch diff --git a/cmd/resource/go.mod b/cmd/resource/go.mod index 9a31f15de..38ca742aa 100644 --- a/cmd/resource/go.mod +++ b/cmd/resource/go.mod @@ -12,11 +12,11 @@ require ( k8s.io/apimachinery v0.0.0-20190913080033-27d36303b655 k8s.io/client-go v0.0.0-20190918160344-1fbdaa4c8d90 sigs.k8s.io/controller-runtime v0.4.0 - sigs.k8s.io/kustomize/kstatus v0.0.0-20191204200457-7c1b477ff62d - sigs.k8s.io/kustomize/kyaml v0.0.0-20191202204815-0a19a5dbd9b8 + sigs.k8s.io/kustomize/kstatus v0.0.0 + sigs.k8s.io/kustomize/kyaml v0.0.0 ) replace ( - sigs.k8s.io/kustomize/kstatus v0.0.0-20191204200457-7c1b477ff62d => ../../kstatus - sigs.k8s.io/kustomize/kyaml v0.0.0-20191202204815-0a19a5dbd9b8 => ../../kyaml + sigs.k8s.io/kustomize/kstatus v0.0.0 => ../../kstatus + sigs.k8s.io/kustomize/kyaml v0.0.0 => ../../kyaml ) diff --git a/cmd/resource/status/cmd/print.go b/cmd/resource/status/cmd/print.go index f1171a710..5f940b334 100644 --- a/cmd/resource/status/cmd/print.go +++ b/cmd/resource/status/cmd/print.go @@ -138,10 +138,10 @@ func (s *TablePrinter) Print() { func (s *TablePrinter) PrintUntil(stop <-chan struct{}, interval time.Duration) <-chan struct{} { completed := make(chan struct{}) + setColor(s.out, WHITE) + s.printTable(s.statusInfo.CurrentStatus(), false) go func() { defer close(completed) - setColor(s.out, WHITE) - s.printTable(s.statusInfo.CurrentStatus(), false) ticker := time.NewTicker(interval) for { select { diff --git a/cmd/resource/status/cmd/wait_test.go b/cmd/resource/status/cmd/wait_test.go index 355fe7510..e74100b51 100644 --- a/cmd/resource/status/cmd/wait_test.go +++ b/cmd/resource/status/cmd/wait_test.go @@ -36,7 +36,7 @@ func TestWaitNoResources(t *testing.T) { aggStatuses := tableOutput.allAggStatuses() expectedAggStatuses := []status.Status{ - status.CurrentStatus, + status.UnknownStatus, status.CurrentStatus, } if !reflect.DeepEqual(aggStatuses, expectedAggStatuses) { diff --git a/releasing/VERSIONS b/releasing/VERSIONS index f3d1109c9..72b345270 100644 --- a/releasing/VERSIONS +++ b/releasing/VERSIONS @@ -1,27 +1,37 @@ #!/usr/bin/env bash # VERSIONS contains the release versions of each kustomize go module -# update this file and run releaseall.sh to cut a new release +# update this file and run releasemodule.sh to cut a new release # kyaml version export kyaml_major=0 export kyaml_minor=0 -export kyaml_patch=5 +export kyaml_patch=6 + +# kstatus version +export kstatus_major=0 +export kstatus_minor=0 +export kstatus_patch=1 # kustomize api version export api_major=0 export api_minor=3 -export api_patch=1 +export api_patch=2 # cmd/config version export cmd_config_major=0 export cmd_config_minor=0 -export cmd_config_patch=5 +export cmd_config_patch=6 # cmd/kubectl version export cmd_kubectl_major=0 export cmd_kubectl_minor=0 -export cmd_kubectl_patch=2 +export cmd_kubectl_patch=3 + +# cmd/resource version +export cmd_resource_major=0 +export cmd_resource_minor=0 +export cmd_resource_patch=1 # kustomize version export kustomize_major=3 diff --git a/releasing/releasemodule.sh b/releasing/releasemodule.sh index c73661680..5361361eb 100755 --- a/releasing/releasemodule.sh +++ b/releasing/releasemodule.sh @@ -67,7 +67,7 @@ function releaseModule { echo "$module complete" } -modules="kyaml api cmd/config cmd/kubectl pluginator kustomize" +modules="kyaml api kstatus cmd/config cmd/resource cmd/kubectl pluginator kustomize" # configure the branch and tag names module="${1?must provide the module to release as an argument: supported modules [$modules]}"