-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
util.go
163 lines (134 loc) · 4.34 KB
/
util.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
/*
Copyright 2019 The Skaffold Authors
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 kubernetes
import (
"bufio"
"context"
"errors"
"fmt"
"io"
"os"
"strings"
k8syaml "k8s.io/apimachinery/pkg/util/yaml"
"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/kubernetes/client"
"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/output/log"
"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/yaml"
)
type yamlObject map[string]interface{}
// These are the required fields for a yaml document to be a valid Kubernetes yaml
var requiredFields = []string{"apiVersion", "kind", "metadata"}
// These are the supported file formats for Kubernetes manifests
var validSuffixes = []string{".yml", ".yaml", ".json"}
// HasKubernetesFileExtension is for determining if a file under a glob pattern
// is deployable file format. It makes no attempt to check whether or not the file
// is actually deployable or has the correct contents.
func HasKubernetesFileExtension(n string) bool {
for _, s := range validSuffixes {
if strings.HasSuffix(n, s) {
return true
}
}
return false
}
// IsKubernetesManifest is for determining if a file is a valid Kubernetes manifest
func IsKubernetesManifest(file string) bool {
_, err := ParseKubernetesObjects(file)
return err == nil
}
// ParseImagesFromKubernetesYaml parses the kubernetes yamls, and if it finds at least one
// valid Kubernetes object, it will return the images referenced in them.
func ParseImagesFromKubernetesYaml(filepath string) ([]string, error) {
k8sObjects, err := ParseKubernetesObjects(filepath)
if err != nil {
return nil, err
}
var images []string
for _, k8sObject := range k8sObjects {
images = append(images, parseImagesFromYaml(k8sObject)...)
}
return images, nil
}
// ParseKubernetesObjects uses required fields from the k8s spec
// to determine if a provided yaml file is a valid k8s manifest, as detailed in
// https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/#required-fields.
// If so, it will return the parsed objects.
func ParseKubernetesObjects(filepath string) ([]yamlObject, error) {
f, err := os.Open(filepath)
if err != nil {
return nil, fmt.Errorf("opening config file: %w", err)
}
defer f.Close()
r := k8syaml.NewYAMLReader(bufio.NewReader(f))
var k8sObjects []yamlObject
for {
doc, err := r.Read()
if err == io.EOF {
break
}
if err != nil {
return nil, fmt.Errorf("reading config file: %w", err)
}
obj := make(yamlObject)
if err := yaml.Unmarshal(doc, &obj); err != nil {
return nil, fmt.Errorf("reading Kubernetes YAML: %w", err)
}
if !hasRequiredK8sManifestFields(obj) {
continue
}
k8sObjects = append(k8sObjects, obj)
}
if len(k8sObjects) == 0 {
return nil, errors.New("no valid Kubernetes objects decoded")
}
return k8sObjects, nil
}
func hasRequiredK8sManifestFields(doc map[string]interface{}) bool {
for _, field := range requiredFields {
if _, ok := doc[field]; !ok {
log.Entry(context.TODO()).Debugf("%s not present in yaml, continuing", field)
return false
}
}
return true
}
// adapted from pkg/skaffold/deploy/kubectl/recursiveReplaceImage()
func parseImagesFromYaml(obj interface{}) []string {
var images []string
switch t := obj.(type) {
case []interface{}:
for _, v := range t {
images = append(images, parseImagesFromYaml(v)...)
}
case yamlObject:
for k, v := range t {
if k != "image" {
images = append(images, parseImagesFromYaml(v)...)
continue
}
if value, ok := v.(string); ok {
images = append(images, value)
}
}
}
return images
}
// FailIfClusterIsNotReachable checks that Kubernetes is reachable.
// This gives a clear early error when the cluster can't be reached.
func FailIfClusterIsNotReachable(kubeContext string) error {
c, err := client.Client(kubeContext)
if err != nil {
return err
}
_, err = c.Discovery().ServerVersion()
return err
}