1/*
2Copyright 2016 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package set
18
19import (
20	"strings"
21
22	"k8s.io/api/core/v1"
23	"k8s.io/apimachinery/pkg/runtime"
24	"k8s.io/apimachinery/pkg/util/sets"
25	"k8s.io/apimachinery/pkg/util/strategicpatch"
26	"k8s.io/cli-runtime/pkg/resource"
27)
28
29// selectContainers allows one or more containers to be matched against a string or wildcard
30func selectContainers(containers []v1.Container, spec string) ([]*v1.Container, []*v1.Container) {
31	out := []*v1.Container{}
32	skipped := []*v1.Container{}
33	for i, c := range containers {
34		if selectString(c.Name, spec) {
35			out = append(out, &containers[i])
36		} else {
37			skipped = append(skipped, &containers[i])
38		}
39	}
40	return out, skipped
41}
42
43// selectString returns true if the provided string matches spec, where spec is a string with
44// a non-greedy '*' wildcard operator.
45// TODO: turn into a regex and handle greedy matches and backtracking.
46func selectString(s, spec string) bool {
47	if spec == "*" {
48		return true
49	}
50	if !strings.Contains(spec, "*") {
51		return s == spec
52	}
53
54	pos := 0
55	match := true
56	parts := strings.Split(spec, "*")
57Loop:
58	for i, part := range parts {
59		if len(part) == 0 {
60			continue
61		}
62		next := strings.Index(s[pos:], part)
63		switch {
64		// next part not in string
65		case next < pos:
66			fallthrough
67		// first part does not match start of string
68		case i == 0 && pos != 0:
69			fallthrough
70		// last part does not exactly match remaining part of string
71		case i == (len(parts)-1) && len(s) != (len(part)+next):
72			match = false
73			break Loop
74		default:
75			pos = next
76		}
77	}
78	return match
79}
80
81// Patch represents the result of a mutation to an object.
82type Patch struct {
83	Info *resource.Info
84	Err  error
85
86	Before []byte
87	After  []byte
88	Patch  []byte
89}
90
91// PatchFn is a function type that accepts an info object and returns a byte slice.
92// Implementations of PatchFn should update the object and return it encoded.
93type PatchFn func(runtime.Object) ([]byte, error)
94
95// CalculatePatch calls the mutation function on the provided info object, and generates a strategic merge patch for
96// the changes in the object. Encoder must be able to encode the info into the appropriate destination type.
97// This function returns whether the mutation function made any change in the original object.
98func CalculatePatch(patch *Patch, encoder runtime.Encoder, mutateFn PatchFn) bool {
99	patch.Before, patch.Err = runtime.Encode(encoder, patch.Info.Object)
100	patch.After, patch.Err = mutateFn(patch.Info.Object)
101	if patch.Err != nil {
102		return true
103	}
104	if patch.After == nil {
105		return false
106	}
107
108	patch.Patch, patch.Err = strategicpatch.CreateTwoWayMergePatch(patch.Before, patch.After, patch.Info.Object)
109	return true
110}
111
112// CalculatePatches calculates patches on each provided info object. If the provided mutateFn
113// makes no change in an object, the object is not included in the final list of patches.
114func CalculatePatches(infos []*resource.Info, encoder runtime.Encoder, mutateFn PatchFn) []*Patch {
115	var patches []*Patch
116	for _, info := range infos {
117		patch := &Patch{Info: info}
118		if CalculatePatch(patch, encoder, mutateFn) {
119			patches = append(patches, patch)
120		}
121	}
122	return patches
123}
124
125func findEnv(env []v1.EnvVar, name string) (v1.EnvVar, bool) {
126	for _, e := range env {
127		if e.Name == name {
128			return e, true
129		}
130	}
131	return v1.EnvVar{}, false
132}
133
134func updateEnv(existing []v1.EnvVar, env []v1.EnvVar, remove []string) []v1.EnvVar {
135	out := []v1.EnvVar{}
136	covered := sets.NewString(remove...)
137	for _, e := range existing {
138		if covered.Has(e.Name) {
139			continue
140		}
141		newer, ok := findEnv(env, e.Name)
142		if ok {
143			covered.Insert(e.Name)
144			out = append(out, newer)
145			continue
146		}
147		out = append(out, e)
148	}
149	for _, e := range env {
150		if covered.Has(e.Name) {
151			continue
152		}
153		covered.Insert(e.Name)
154		out = append(out, e)
155	}
156	return out
157}
158