istio
305 строк · 9.3 Кб
1// Copyright Istio Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package config
16
17import (
18"encoding/json"
19"errors"
20"fmt"
21"math"
22"strings"
23"time"
24)
25
26type ResourceType int
27
28const (
29Namespace ResourceType = iota
30Deployment
31Pod
32Label
33Annotation
34Container
35)
36
37// SelectionSpec is a spec for pods that will be Include in the capture
38// archive. The format is:
39//
40// Namespace1,Namespace2../Deployments/Pods/Label1,Label2.../Annotation1,Annotation2.../ContainerName1,ContainerName2...
41//
42// Namespace, pod and container names are pattern matching while labels
43// and annotations may have pattern in the values with exact match for keys.
44// All labels and annotations in the list must match.
45// All fields are optional, if they are not specified, all values match.
46// Pattern matching style is glob.
47// Exclusions have a higher precedence than inclusions.
48// Ordering defines pod priority for cases where the archive exceeds the maximum
49// size and some logs must be dropped.
50//
51// Examples:
52//
53// 1. All pods in test-namespace with label "test=foo" but without label "private" (with any value):
54//
55// include:
56// test-namespace/*/*/test=foo
57// exclude:
58// test-namespace/*/*/private
59//
60// 2. Pods in all namespaces except "kube-system" with annotation "revision"
61// matching wildcard 1.6*:
62//
63// exclude:
64// kube-system/*/*/*/revision=1.6*
65//
66// 3. Pods with "prometheus" in the name, except those with
67// the annotation "internal=true":
68//
69// include:
70// */*/*prometheus*
71// exclude:
72// */*/*prometheus*/*/internal=true
73//
74// 4. Container logs for all containers called "istio-proxy":
75//
76// include:
77// */*/*/*/*/istio-proxy
78type SelectionSpec struct {
79Namespaces []string `json:"namespaces,omitempty"`
80Deployments []string `json:"deployments,omitempty"`
81Daemonsets []string `json:"daemonsets,omitempty"`
82Pods []string `json:"pods,omitempty"`
83Containers []string `json:"containers,omitempty"`
84Labels map[string]string `json:"labels,omitempty"`
85Annotations map[string]string `json:"annotations,omitempty"`
86}
87
88type SelectionSpecs []*SelectionSpec
89
90func (s SelectionSpecs) String() string {
91var out []string
92for _, ss := range s {
93st := ""
94if !defaultListSetting(ss.Namespaces) {
95st += fmt.Sprintf("Namespaces: %s", strings.Join(ss.Namespaces, ","))
96}
97if !defaultListSetting(ss.Deployments) {
98st += fmt.Sprintf("/Deployments: %s", strings.Join(ss.Deployments, ","))
99}
100if !defaultListSetting(ss.Pods) {
101st += fmt.Sprintf("/Pods:%s", strings.Join(ss.Pods, ","))
102}
103if !defaultListSetting(ss.Containers) {
104st += fmt.Sprintf("/Containers: %s", strings.Join(ss.Containers, ","))
105}
106if len(ss.Labels) > 0 {
107st += fmt.Sprintf("/Labels: %v", ss.Labels)
108}
109if len(ss.Annotations) > 0 {
110st += fmt.Sprintf("/Annotations: %v", ss.Annotations)
111}
112out = append(out, "{ "+st+" }")
113}
114return strings.Join(out, " AND ")
115}
116
117func defaultListSetting(s []string) bool {
118if len(s) < 1 {
119return true
120}
121if len(s) == 1 {
122return strings.TrimSpace(s[0]) == "" || s[0] == "*"
123}
124return false
125}
126
127// BugReportConfig controls what is captured and Include in the kube-capture tool
128// archive.
129type BugReportConfig struct {
130// KubeConfigPath is the path to kube config file.
131KubeConfigPath string `json:"kubeConfigPath,omitempty"`
132// Context is the cluster Context in the kube config
133Context string `json:"context,omitempty"`
134
135// IstioNamespace is the namespace where the istio control plane is installed.
136IstioNamespace string `json:"istioNamespace,omitempty"`
137
138// DryRun controls whether logs are actually captured and saved.
139DryRun bool `json:"dryRun,omitempty"`
140
141// FullSecrets controls whether secret contents are included.
142FullSecrets bool `json:"fullSecrets,omitempty"`
143
144// CommandTimeout is the maximum amount of time running the command
145// before giving up, even if not all logs are captured. Upon timeout,
146// the command creates an archive with only the logs captured so far.
147CommandTimeout Duration `json:"commandTimeout,omitempty"`
148
149// Include is a list of SelectionSpec entries for resources to include.
150Include SelectionSpecs `json:"include,omitempty"`
151// Exclude is a list of SelectionSpec entries for resources t0 exclude.
152Exclude SelectionSpecs `json:"exclude,omitempty"`
153
154// StartTime is the start time the log capture time range.
155// If set, Since must be unset.
156StartTime time.Time `json:"startTime,omitempty"`
157// EndTime is the end time the log capture time range.
158// Default is now.
159EndTime time.Time `json:"endTime,omitempty"`
160// Since defines the start time the log capture time range.
161// StartTime is set to EndTime - Since.
162// If set, StartTime must be unset.
163Since Duration `json:"since,omitempty"`
164
165// TimeFilterApplied stores if user has provided any time filtering flags.
166// If Since, StartTime, EndTime are all not applied by the user, set TimeFilterApplied as false; Otherwise set true
167TimeFilterApplied bool `json:"timeFilterApplied,omitempty"`
168
169// CriticalErrors is a list of glob pattern matches for errors that,
170// if found in a log, set the highest priority for the log to ensure
171// that it is Include in the capture archive.
172CriticalErrors []string `json:"criticalErrors,omitempty"`
173// IgnoredErrors are glob error patterns which are ignored when
174// calculating the error heuristic for a log.
175IgnoredErrors []string `json:"ignoredErrors,omitempty"`
176
177// RequestConcurrency controls the request concurrency limit to the API server.
178RequestConcurrency int `json:"requestConcurrency,omitempty"`
179}
180
181func (b *BugReportConfig) String() string {
182out := ""
183if b.KubeConfigPath != "" {
184out += fmt.Sprintf("kubeconfig: %s\n", b.KubeConfigPath)
185}
186if b.Context != "" {
187out += fmt.Sprintf("context: %s\n", b.Context)
188}
189out += fmt.Sprintf("istio-namespace: %s\n", b.IstioNamespace)
190out += fmt.Sprintf("full-secrets: %v\n", b.FullSecrets)
191out += fmt.Sprintf("timeout (mins): %v\n", math.Round(float64(int(b.CommandTimeout))/float64(time.Minute)))
192out += fmt.Sprintf("include: %s\n", b.Include)
193out += fmt.Sprintf("exclude: %s\n", b.Exclude)
194if !b.StartTime.Equal(time.Time{}) {
195out += fmt.Sprintf("start-time: %v\n", b.StartTime)
196}
197out += fmt.Sprintf("end-time: %v\n", b.EndTime)
198if b.Since != 0 {
199out += fmt.Sprintf("since: %v\n", b.Since)
200}
201return out
202}
203
204func parseToIncludeTypeSlice(s string) []string {
205if strings.TrimSpace(s) == "*" || s == "" {
206return nil
207}
208return strings.Split(s, ",")
209}
210
211func parseToIncludeTypeMap(s string) (map[string]string, error) {
212if strings.TrimSpace(s) == "*" {
213return nil, nil
214}
215out := make(map[string]string)
216for _, ss := range strings.Split(s, ",") {
217if len(ss) == 0 {
218continue
219}
220kv := strings.Split(ss, "=")
221if len(kv) != 2 {
222return nil, fmt.Errorf("bad label/annotation selection %s, must have format key=value", ss)
223}
224if strings.Contains(kv[0], "*") {
225return nil, fmt.Errorf("bad label/annotation selection %s, key cannot have '*' wildcards", ss)
226}
227out[kv[0]] = kv[1]
228}
229return out, nil
230}
231
232func (s *SelectionSpec) UnmarshalJSON(b []byte) error {
233ft := []ResourceType{Namespace, Deployment, Pod, Label, Annotation, Container}
234str := strings.TrimPrefix(strings.TrimSuffix(string(b), `"`), `"`)
235for i, f := range strings.Split(str, "/") {
236var err error
237switch ft[i] {
238case Namespace:
239s.Namespaces = parseToIncludeTypeSlice(f)
240case Deployment:
241s.Deployments = parseToIncludeTypeSlice(f)
242case Pod:
243s.Pods = parseToIncludeTypeSlice(f)
244case Label:
245s.Labels, err = parseToIncludeTypeMap(f)
246if err != nil {
247return err
248}
249case Annotation:
250s.Annotations, err = parseToIncludeTypeMap(f)
251if err != nil {
252return err
253}
254case Container:
255s.Containers = parseToIncludeTypeSlice(f)
256}
257}
258
259return nil
260}
261
262func (s *SelectionSpec) MarshalJSON() ([]byte, error) {
263out := fmt.Sprint(strings.Join(s.Namespaces, ","))
264out += fmt.Sprintf("/%s", strings.Join(s.Deployments, ","))
265out += fmt.Sprintf("/%s", strings.Join(s.Pods, ","))
266tmp := []string{}
267for k, v := range s.Labels {
268tmp = append(tmp, fmt.Sprintf("%s=%s", k, v))
269}
270out += fmt.Sprintf("/%s", strings.Join(tmp, ","))
271tmp = []string{}
272for k, v := range s.Annotations {
273tmp = append(tmp, fmt.Sprintf("%s=%s", k, v))
274}
275out += fmt.Sprintf("/%s", strings.Join(tmp, ","))
276out += fmt.Sprintf("/%s", strings.Join(s.Containers, ","))
277return []byte(`"` + out + `"`), nil
278}
279
280type Duration time.Duration
281
282func (d Duration) MarshalJSON() ([]byte, error) {
283return json.Marshal(time.Duration(d).String())
284}
285
286func (d *Duration) UnmarshalJSON(b []byte) error {
287var v any
288if err := json.Unmarshal(b, &v); err != nil {
289return err
290}
291switch value := v.(type) {
292case float64:
293*d = Duration(time.Duration(value))
294return nil
295case string:
296tmp, err := time.ParseDuration(value)
297if err != nil {
298return err
299}
300*d = Duration(tmp)
301return nil
302default:
303return errors.New("invalid duration")
304}
305}
306