1
0
mirror of https://github.com/ko-build/ko.git synced 2025-02-01 19:14:40 +02:00
ko-build/pkg/resolve/selector.go
2019-07-23 09:04:40 -07:00

138 lines
4.3 KiB
Go

// 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 == ""
}