/
util.go
252 lines (213 loc) · 6.32 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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
package utils
import (
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"os"
"reflect"
"strings"
netUrl "net/url"
logger "github.com/sirupsen/logrus"
BackplaneApi "github.com/openshift/backplane-api/pkg/client"
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/tools/clientcmd/api"
"github.com/openshift/backplane-cli/internal/github"
"github.com/openshift/backplane-cli/pkg/info"
)
const (
ClustersPageSize = 50
BackplaneAPIURLRegexp string = `(?mi)^https:\/\/api\.(.*)backplane\.(.*)`
ClusterIDRegexp string = "/?backplane/cluster/([a-zA-Z0-9]+)/?"
)
var (
defaultKubeConfig = api.Config{
Kind: "Config",
APIVersion: "v1",
Preferences: api.Preferences{},
Clusters: map[string]*api.Cluster{
"dummy_cluster": {
Server: "https://api-backplane.apps.something.com/backplane/cluster/configcluster",
},
},
Contexts: map[string]*api.Context{
"default/test123/anonymous": {
Cluster: "dummy_cluster",
Namespace: "default",
},
},
CurrentContext: "default/test123/anonymous",
}
defaultKubeConfigFileName = "config"
)
// GetFreePort asks the OS for an available port to listen to.
// https://github.com/phayes/freeport/blob/master/freeport.go
func GetFreePort() (int, error) {
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
if err != nil {
return 0, err
}
l, err := net.ListenTCP("tcp", addr)
if err != nil {
return 0, err
}
defer l.Close()
return l.Addr().(*net.TCPAddr).Port, nil
}
// CheckHealth check if the given url returns http status 200
// return false if it not 200 or encounter any error.
func CheckHealth(url string) bool {
// Parse the given URL and check for ambiguities
parsedURL, err := netUrl.Parse(url)
if err != nil {
return false //just return false for any error
}
resp, err := http.Get(parsedURL.String())
if err != nil {
return false //just return false for any error
}
return resp.StatusCode == http.StatusOK
}
func ReadKubeconfigRaw() (api.Config, error) {
return genericclioptions.NewConfigFlags(true).ToRawKubeConfigLoader().RawConfig()
}
// MatchBaseDomain returns true if the given longHostname matches the baseDomain.
func MatchBaseDomain(longHostname, baseDomain string) bool {
if len(baseDomain) == 0 {
return true
}
hostnameSegs := strings.Split(longHostname, ".")
baseSegs := strings.Split(baseDomain, ".")
if len(hostnameSegs) < len(baseSegs) {
return false
}
cmpSegs := hostnameSegs[len(hostnameSegs)-len(baseSegs):]
return reflect.DeepEqual(cmpSegs, baseSegs)
}
func TryParseBackplaneAPIError(rsp *http.Response) (*BackplaneApi.Error, error) {
bodyBytes, err := io.ReadAll(rsp.Body)
defer func() { _ = rsp.Body.Close() }()
if err != nil {
return nil, err
}
var dest BackplaneApi.Error
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
return nil, err
}
return &dest, nil
}
func TryRenderErrorRaw(rsp *http.Response) error {
data, err := TryParseBackplaneAPIError(rsp)
if err != nil {
return err
}
return RenderJSONBytes(data)
}
func GetFormattedError(rsp *http.Response) error {
data, err := TryParseBackplaneAPIError(rsp)
if err != nil {
return err
}
if data.Message != nil && data.StatusCode != nil {
return fmt.Errorf("error from backplane: \n Status Code: %d\n Message: %s", *data.StatusCode, *data.Message)
} else {
return fmt.Errorf("error from backplane: \n Status Code: %d\n Message: %s", rsp.StatusCode, rsp.Status)
}
}
func TryPrintAPIError(rsp *http.Response, rawFlag bool) error {
if rawFlag {
if err := TryRenderErrorRaw(rsp); err != nil {
return fmt.Errorf("unable to parse error from backplane: \n Status Code: %d", rsp.StatusCode)
} else {
return nil
}
} else {
return GetFormattedError(rsp)
}
}
func ParseParamsFlag(paramsFlag []string) (map[string]string, error) {
var result = map[string]string{}
for _, s := range paramsFlag {
keyVal := strings.Split(s, "=")
if len(keyVal) >= 2 {
key := strings.TrimSpace(keyVal[0])
value := strings.TrimSpace(strings.Join(keyVal[1:], ""))
result[key] = value
} else {
return nil, fmt.Errorf("error parsing params flag, %s", s)
}
}
return result, nil
}
func CreateTempKubeConfig(kubeConfig *api.Config) error {
f, err := os.CreateTemp("", defaultKubeConfigFileName)
if err != nil {
return err
}
// set default kube config if values are empty
if kubeConfig == nil {
kubeConfig = &defaultKubeConfig
}
err = clientcmd.WriteToFile(*kubeConfig, f.Name())
if err != nil {
return err
}
err = f.Close()
if err != nil {
return err
}
// set kube config env with temp kube config file
os.Setenv("KUBECONFIG", f.Name())
return nil
}
// GetDefaultKubeConfig return default kube config
func GetDefaultKubeConfig() api.Config {
return defaultKubeConfig
}
// ModifyTempKubeConfigFileName update default temp kube config file name
func ModifyTempKubeConfigFileName(fileName string) error {
defaultKubeConfigFileName = fileName
return nil
}
func RemoveTempKubeConfig() {
path, found := os.LookupEnv("KUBECONFIG")
if found {
os.Remove(path)
}
}
// CheckBackplaneVersion checks the backplane version and aims to only
// report any errors encountered in the process in order to
// avoid calling functions act as usual
func CheckBackplaneVersion(cmd *cobra.Command) {
if cmd == nil {
logger.Debugln("Command object is nil")
return
}
ctx := cmd.Context()
if ctx == nil {
logger.Debugln("Context object is nil")
return
}
git := github.NewClient()
if err := git.CheckConnection(); err != nil {
logger.WithField("Connection error", err).Warn("Could not connect to GitHub")
return
}
// Get the latest version from the GitHub API
latestVersionTag, err := git.GetLatestVersion(ctx)
if err != nil {
logger.WithField("Fetch error", err).Warn("Could not fetch latest version from GitHub")
return
}
// GitHub API keeps the v prefix in front which causes mismatch with info.Version
latestVersion := strings.TrimLeft(latestVersionTag.TagName, "v")
// Check if the local version is already up-to-date
if latestVersion == info.Version {
logger.WithField("Current version", info.Version).Info("Already up-to-date")
return
}
logger.WithField("Current version", info.Version).WithField("Latest version", latestVersion).Warn("Your Backplane CLI is not up to date. Please run the command 'ocm backplane upgrade' to upgrade to the latest version")
}