// Copyright 2018 Google LLC All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package resolve import ( "bytes" "fmt" "regexp" "strings" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" yaml2json "k8s.io/apimachinery/pkg/util/yaml" "sigs.k8s.io/yaml" ) // FilterBySelector filters out any resources // from the raw manifest bytes whose labels // don't match the provided selector func FilterBySelector(input []byte, selectorString string) ([]byte, error) { selector, err := labels.Parse(selectorString) if err != nil { return nil, err } var outputObjectsYaml [][]byte // parse runtime.Objects from the input yaml objects, err := parseUnstructured(input) if err != nil { return nil, err } for _, object := range objects { // objects parsed by UnstructuredJSONScheme can only be of // type *unstructured.Unstructured or *unstructured.UnstructuredList switch unstructuredObj := object.obj.(type) { case *unstructured.Unstructured: // append the object if it matches the provided labels if selector.Matches(labels.Set(unstructuredObj.GetLabels())) { outputObjectsYaml = append(outputObjectsYaml, object.yaml) } case *unstructured.UnstructuredList: // filter the list items based on label var filteredItems []unstructured.Unstructured for _, obj := range unstructuredObj.Items { if selector.Matches(labels.Set(obj.GetLabels())) { filteredItems = append(filteredItems, obj) } } // only append the list if it still contains items after being filtered switch len(filteredItems) { case 0: // the whole list was filtered, omit it from the resultant yaml continue case len(unstructuredObj.Items): // nothing was filtered from the list, use the original yaml outputObjectsYaml = append(outputObjectsYaml, object.yaml) default: unstructuredObj.Items = filteredItems // list was partially filtered, we need to re-marshal it rawJson, err := runtime.Encode(unstructured.UnstructuredJSONScheme, unstructuredObj) if err != nil { return nil, err } rawYaml, err := yaml.JSONToYAML(rawJson) if err != nil { return nil, err } outputObjectsYaml = append(outputObjectsYaml, rawYaml) } default: panic(fmt.Sprintf("unknown object type %T parsed from yaml: \n%v ", object.obj, object.yaml)) } } // re-join the objects into a single manifest return bytes.Join(outputObjectsYaml, []byte("\n---")), nil } var yamlSeparatorRegex = regexp.MustCompile("\n---") // a tuple to represent a kubernetes object along with the original yaml snippet it was parsed from type objectYamlTuple struct { obj runtime.Object yaml []byte } func parseUnstructured(rawYaml []byte) ([]objectYamlTuple, error) { objectYamls := yamlSeparatorRegex.Split(string(rawYaml), -1) var resources []objectYamlTuple for _, objectYaml := range objectYamls { // empty yaml snippets, such as those which can be // generated by helm should be ignored // else they may be parsed into empty map[string]interface{} objects if isEmptyYamlSnippet(objectYaml) { continue } jsn, err := yaml2json.ToJSON([]byte(objectYaml)) if err != nil { return nil, err } runtimeObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, jsn) if err != nil { return nil, err } resources = append(resources, objectYamlTuple{obj: runtimeObj, yaml: []byte(objectYaml)}) } return resources, nil } var commentRegex = regexp.MustCompile("#.*") func isEmptyYamlSnippet(objYaml string) bool { removeComments := commentRegex.ReplaceAllString(objYaml, "") removeNewlines := strings.Replace(removeComments, "\n", "", -1) removeDashes := strings.Replace(removeNewlines, "---", "", -1) removeSpaces := strings.Replace(removeDashes, " ", "", -1) return removeSpaces == "" }