This repository has been archived by the owner on Feb 5, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathversions.go
171 lines (149 loc) · 5.61 KB
/
versions.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
164
165
166
167
168
169
170
171
// Copyright 2017 CoreOS Inc.
//
// 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 internal
import (
"fmt"
"io/ioutil"
"strings"
"github.com/coreos/go-semver/semver"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
yaml "gopkg.in/yaml.v2"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
)
const (
// CluoRuntimeMappings is the default path for hook runtime-mappings (tectonic-cluo ConfigMap)
CluoRuntimeMappings = "/etc/runtime-mappings.yaml"
// InstallerRuntimeMappings is the default path for bootstrapper runtime-mappings (installer file)
InstallerRuntimeMappings = "/etc/kubernetes/installer/runtime-mappings.yaml"
// versionManifestKind is the current type for the runtime-mappings YAML object
versionManifestKind = "VersionManifestV1"
// configMapNamespace is the default namespace for runtime-mappings ConfigMap
configMapNamespace = "tectonic-system"
// configMapName is the default name for the runtime-mappings ConfigMap
configMapName = "tectonic-torcx-runtime-mappings"
// configMapKey is the default object/key in the runtime-mappings ConfigMap
configMapKey = "runtime-mappings.yaml"
)
type VersionManifest struct {
Kind string `yaml:"kind"`
Versions map[string]Dep `yaml:"versions"`
}
type Dep map[string]map[string][]string
// VersionFor parses the version manifest file and returns the list of preferred
// package versions for a given k8s version. The returned value will never be
// empty if error is nil.
func (a *App) VersionFor(localOnly bool, name, k8sVersion string) ([]string, error) {
// TODO(lucab): consider caching this manifest if we grow to
// more components other than docker.
m, err := a.GetVersionManifest(localOnly)
if err != nil {
return nil, err
}
// The k8s version is something like "v1.6.7+coreos.0"
// Trim it to "1.6"
k8sVersion = strings.TrimLeft(k8sVersion, "v")
ver, err := semver.NewVersion(k8sVersion)
if err != nil {
return nil, errors.Wrap(err, "failed to parse k8s version")
}
k8sVersion = fmt.Sprintf("%d.%d", ver.Major, ver.Minor)
return m.VersionFor("k8s", k8sVersion, name)
}
// VersionFor is the actual version lookup logic.
func (m *VersionManifest) VersionFor(haveName, haveVersion, wantName string) ([]string, error) {
// Try and find the package + version
h, ok := m.Versions[haveName]
if !ok {
return nil, fmt.Errorf("Version manifest has no versions for %s", haveName)
}
hv, ok := h[haveVersion]
if !ok {
return nil, fmt.Errorf("Version manifest has no entries for %s version %s", haveName, haveVersion)
}
wv, ok := hv[wantName]
if !ok || len(wv) == 0 {
return nil, fmt.Errorf("Version manifest for %s version %s doesn't specify %s", haveName, haveVersion, wantName)
}
return wv, nil
}
// Parse and quickly validate the yaml version manifest
func parseVersionManifest(data []byte) (*VersionManifest, error) {
m := VersionManifest{}
err := yaml.Unmarshal(data, &m)
if err != nil {
return nil, errors.Wrap(err, "failed to parse version manifest")
}
if m.Kind != versionManifestKind {
return nil, fmt.Errorf("did not understand version kind %s", m.Kind)
}
return &m, nil
}
// GetVersionManifest parses the version manifest file supplied by the user.
func (a *App) GetVersionManifest(localOnly bool) (*VersionManifest, error) {
path := a.Conf.VersionManifestPath
if path == "" {
return nil, errors.New("missing version manifest path")
}
// Conditionally try ConfigMap from api-server first (bootstrapper only)
if !localOnly {
logrus.Debug("Querying api-server for runtime mappings ConfigMap")
manifest, err := a.versionManifestFromAPIServer()
if err == nil {
return parseVersionManifest([]byte(manifest))
}
logrus.Warnf("Failed to query api-server for ConfigMap: %s", err)
}
// Source mappings from local file
data, err := ioutil.ReadFile(path)
if err != nil {
return nil, errors.Wrapf(err, "Failed to read runtime mappings from %q", path)
}
return parseVersionManifest(data)
}
// versionManifestFromAPIServer connects to the APIServer and determines
// runtime mappings from the relevant ConfigMap.
func (a *App) versionManifestFromAPIServer() (string, error) {
config, err := clientcmd.BuildConfigFromFlags("", a.Conf.Kubeconfig)
if err != nil {
return "", errors.Wrap(err, "failed to build kubeconfig")
}
client, err := kubernetes.NewForConfig(config)
if err != nil {
return "", errors.Wrap(err, "failed to build kube client")
}
var versionManifest string
err = retry(3, 10, func() error {
cmi := client.ConfigMaps(configMapNamespace)
if cmi == nil {
return errors.Errorf("nil ConfigMapInterface for namespace %s", configMapNamespace)
}
cm, e := cmi.Get(configMapName, meta_v1.GetOptions{})
if e != nil {
return e
}
if cm == nil || cm.Data[configMapKey] == "" {
return errors.Errorf("missing entry %s/%s", configMapName, configMapKey)
}
versionManifest = cm.Data[configMapKey]
return nil
})
if err != nil {
return "", errors.Wrapf(err, "failed to get ConfigMap %s/%s", configMapNamespace, configMapName)
}
logrus.Debugf("Got %s from ConfigMap %s/%s", configMapKey, configMapNamespace, configMapName)
return versionManifest, nil
}