-
Notifications
You must be signed in to change notification settings - Fork 98
/
register.go
544 lines (483 loc) · 24 KB
/
register.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
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
package register
import (
"encoding/json"
"fmt"
"github.com/open-horizon/anax/api"
"github.com/open-horizon/anax/apicommon"
"github.com/open-horizon/anax/cli/cliconfig"
"github.com/open-horizon/anax/cli/cliutils"
cliexchange "github.com/open-horizon/anax/cli/exchange"
"github.com/open-horizon/anax/cutil"
"github.com/open-horizon/anax/exchange"
"github.com/open-horizon/anax/externalpolicy"
"github.com/open-horizon/anax/i18n"
"github.com/open-horizon/anax/persistence"
"github.com/open-horizon/anax/semanticversion"
"io/ioutil"
"net/http"
"os"
"regexp"
"strings"
)
// These structs are used to parse the registration input file. These are also used by the hzn dev code.
type GlobalSet struct {
Type string `json:"type"`
ServiceSpecs persistence.ServiceSpecs `json:"service_specs,omitempty"`
Variables map[string]interface{} `json:"variables"`
}
func (g GlobalSet) String() string {
return fmt.Sprintf("Global Array element, type: %v, service_specs: %v, variables: %v", g.Type, g.ServiceSpecs, g.Variables)
}
// Use for services. This is used by other cli sub-cmds too.
type MicroWork struct {
Org string `json:"org"`
Url string `json:"url"`
VersionRange string `json:"versionRange,omitempty"` //optional
Variables map[string]interface{} `json:"variables"`
}
func (m MicroWork) String() string {
return fmt.Sprintf("Org: %v, URL: %v, VersionRange: %v, Variables: %v", m.Org, m.Url, m.VersionRange, m.Variables)
}
type InputFile struct {
Global []GlobalSet `json:"global,omitempty"`
Services []MicroWork `json:"services,omitempty"`
}
func ReadInputFile(filePath string, inputFileStruct *InputFile) {
newBytes := cliconfig.ReadJsonFileWithLocalConfig(filePath)
err := json.Unmarshal(newBytes, inputFileStruct)
if err != nil {
cliutils.Fatal(cliutils.JSON_PARSING_ERROR, i18n.GetMessagePrinter().Sprintf("failed to unmarshal json input file %s: %v", filePath, err))
}
}
type ExchangeNodes struct {
LastIndex int `json:"lastIndex"`
Nodes map[string]exchange.Device `json:"nodes"`
}
// read and verify a node policy file
func ReadAndVerifyPolicFile(jsonFilePath string, nodePol *externalpolicy.ExternalPolicy) {
// get message printer
msgPrinter := i18n.GetMessagePrinter()
newBytes := cliconfig.ReadJsonFileWithLocalConfig(jsonFilePath)
err := json.Unmarshal(newBytes, nodePol)
if err != nil {
cliutils.Fatal(cliutils.JSON_PARSING_ERROR, msgPrinter.Sprintf("failed to unmarshal json input file %s: %v", jsonFilePath, err))
}
//Check the policy file format
err = nodePol.Validate()
if err != nil {
cliutils.Fatal(cliutils.CLI_INPUT_ERROR, msgPrinter.Sprintf("Incorrect node policy format in file %s: %v", jsonFilePath, err))
}
}
// DoIt registers this node to Horizon with a pattern
func DoIt(org, pattern, nodeIdTok, userPw, email, inputFile string, nodeOrgFromFlag string, patternFromFlag string, nodeName string, nodepolicyFlag string) {
// get message printer
msgPrinter := i18n.GetMessagePrinter()
// check the input
org, pattern = verifyRegisterParamters(org, pattern, nodeOrgFromFlag, patternFromFlag)
cliutils.SetWhetherUsingApiKey(nodeIdTok) // if we have to use userPw later in NodeCreate(), it will set this appropriately for userPw
// Read input file 1st, so we don't get half way thru registration before finding the problem
inputFileStruct := InputFile{}
if inputFile != "" {
msgPrinter.Printf("Reading input file %s...", inputFile)
msgPrinter.Println()
ReadInputFile(inputFile, &inputFileStruct)
}
// read and verify the node policy if it specified
var nodePol externalpolicy.ExternalPolicy
if nodepolicyFlag != "" {
ReadAndVerifyPolicFile(nodepolicyFlag, &nodePol)
}
// get the arch from anax
statusInfo := apicommon.Info{}
cliutils.HorizonGet("status", []int{200}, &statusInfo, false)
anaxArch := (*statusInfo.Configuration).Arch
// Get the exchange url from the anax api and the cli. Display a warning if they do not match.
exchUrlBase := cliutils.GetExchangeUrl()
anaxExchUrlBase := strings.TrimSuffix(cliutils.GetExchangeUrlFromAnax(), "/")
if exchUrlBase != anaxExchUrlBase && exchUrlBase != "" && anaxExchUrlBase != "" {
cliutils.Fatal(cliutils.CLI_INPUT_ERROR, msgPrinter.Sprintf("hzn cli is configured with exchange url %s from %s and the horizon agent is configured with exchange url %s from %s. hzn register will not work with mismatched exchange urls.", exchUrlBase, cliutils.GetExchangeUrlLocation(), anaxExchUrlBase, cliutils.GetExchangeUrlLocationFromAnax()))
} else {
msgPrinter.Printf("Horizon Exchange base URL: %s", exchUrlBase)
msgPrinter.Println()
}
// Get node info from anax
horDevice := api.HorizonDevice{}
cliutils.HorizonGet("node", []int{200}, &horDevice, false)
// exit if the node is already registered
if horDevice.Config != nil && horDevice.Config.State != nil && (*horDevice.Config.State != persistence.CONFIGSTATE_UNCONFIGURED) {
cliutils.Fatal(cliutils.HTTP_ERROR, msgPrinter.Sprintf("this Horizon node is already registered or in the process of being registered. If you want to register it differently, run 'hzn unregister' first."))
}
// Default node id and token if necessary
nodeId, nodeToken := cliutils.SplitIdToken(nodeIdTok)
if nodeId == "" {
// Get the id from anax
if horDevice.Id == nil {
cliutils.Fatal(cliutils.ANAX_NOT_CONFIGURED_YET, msgPrinter.Sprintf("Failed to get proper response from the Horizon agent"))
}
nodeId = *horDevice.Id
if nodeId == "" {
cliutils.Fatal(cliutils.CLI_INPUT_ERROR, msgPrinter.Sprintf("Please specify the node id and token using -n flag or HZN_EXCHANGE_NODE_AUTH environment variable."))
} else {
msgPrinter.Printf("Using node ID '%s' from the Horizon agent", nodeId)
msgPrinter.Println()
}
} else {
// trim the org off the node id. the HZN_EXCHANGE_NODE_AUTH may contain the org id.
_, nodeId = cliutils.TrimOrg(org, nodeId)
}
if nodeToken == "" {
// Create a random token
var err error
nodeToken, err = cutil.SecureRandomString()
if err != nil {
cliutils.Fatal(cliutils.INTERNAL_ERROR, msgPrinter.Sprintf("could not create a random token"))
}
msgPrinter.Printf("Generated random node token")
msgPrinter.Println()
}
nodeIdTok = nodeId + ":" + nodeToken
if nodeName == "" {
nodeName = nodeId
}
// See if the node exists in the exchange, and create if it doesn't
var nodes ExchangeNodes
exchangePattern := ""
httpCode := cliutils.ExchangeGet("Exchange", exchUrlBase, "orgs/"+org+"/nodes/"+nodeId, cliutils.OrgAndCreds(org, nodeIdTok), nil, &nodes)
if httpCode != 200 {
if userPw == "" {
cliutils.Fatal(cliutils.CLI_INPUT_ERROR, msgPrinter.Sprintf("node '%s/%s' does not exist in the exchange with the specified token, and the -u flag was not specified to provide exchange user credentials to create/update it.", org, nodeId))
}
msgPrinter.Printf("Node %s/%s does not exist in the exchange with the specified token, creating/updating it...", org, nodeId)
msgPrinter.Println()
cliexchange.NodeCreate(org, "", nodeId, nodeToken, userPw, email, anaxArch, nodeName)
} else {
msgPrinter.Printf("Node %s/%s exists in the exchange", org, nodeId)
for _, n := range nodes.Nodes {
exchangePattern = n.Pattern
break
}
msgPrinter.Println()
}
// Use the exchange node pattern if any
if pattern == "" {
if exchangePattern == "" {
if nodepolicyFlag == "" {
msgPrinter.Printf("No pattern or node policy is specified. Will proceeed with the existing node policy.")
msgPrinter.Println()
} else {
msgPrinter.Printf("Will proceeed with the given node policy.")
msgPrinter.Println()
}
} else {
msgPrinter.Printf("Pattern %s defined for the node on the exchange. Will proceeed with this pattern.", exchangePattern)
msgPrinter.Println()
pattern = exchangePattern
}
} else {
if exchangePattern != "" && cliutils.AddOrg(org, pattern) != cliutils.AddOrg(org, exchangePattern) {
cliutils.Fatal(cliutils.CLI_INPUT_ERROR, msgPrinter.Sprintf("Cannot proceed with the given pattern %s because it is different from the pattern %s defined for the node in the exchange.", pattern, exchangePattern))
} else {
var output exchange.GetPatternResponse
var patorg, patname string
patorg, patname = cliutils.TrimOrg(org, pattern)
httpCode := cliutils.ExchangeGet("Exchange", exchUrlBase, "orgs/"+patorg+"/patterns"+cliutils.AddSlash(patname), cliutils.OrgAndCreds(org, nodeIdTok), []int{200, 404, 405}, &output)
if httpCode != 200 {
cliutils.Fatal(cliutils.NOT_FOUND, msgPrinter.Sprintf("pattern '%s/%s' not found from the exchange.", patorg, patname))
}
pat := output.Patterns[patorg+"/"+patname]
if len(pat.Services) == 0 {
cliutils.Fatal(cliutils.CLI_INPUT_ERROR, msgPrinter.Sprintf("Cannot proceed with the given pattern %s because it does not include any services.", pattern))
} else {
msgPrinter.Printf("Will proceeed with the given pattern %s.", pattern)
msgPrinter.Println()
}
}
}
// Update node policy if specified
if nodepolicyFlag != "" {
msgPrinter.Printf("Updating the node policy...")
msgPrinter.Println()
cliutils.ExchangePutPost("Exchange", http.MethodPut, cliutils.GetExchangeUrl(), "orgs/"+org+"/nodes/"+nodeId+"/policy", cliutils.OrgAndCreds(org, nodeIdTok), []int{201}, nodePol)
}
// Initialize the Horizon device (node)
msgPrinter.Printf("Initializing the Horizon node...")
msgPrinter.Println()
//nd := Node{Id: nodeId, Token: nodeToken, Org: org, Pattern: pattern, Name: nodeId, HA: false}
falseVal := false
nd := api.HorizonDevice{Id: &nodeId, Token: &nodeToken, Org: &org, Pattern: &pattern, Name: &nodeName, HA: &falseVal} //todo: support HA config
httpCode, _ = cliutils.HorizonPutPost(http.MethodPost, "node", []int{201, 200, cliutils.ANAX_ALREADY_CONFIGURED}, nd)
if httpCode == cliutils.ANAX_ALREADY_CONFIGURED {
// Note: I wanted to make `hzn register` idempotent, but the anax api doesn't support changing existing settings once in configuring state (to maintain internal consistency).
// And i can't query ALL the existing settings to make sure they are what we were going to set, because i can't query the node token.
cliutils.Fatal(cliutils.HTTP_ERROR, msgPrinter.Sprintf("this Horizon node is already registered or in the process of being registered. If you want to register it differently, run 'hzn unregister' first."))
}
// Process the input file and call /attribute, /service/config to set the specified variables
if inputFile != "" {
// Set the global variables as attributes with no url (or in the case of HTTPSBasicAuthAttributes, with url equal to image svr)
// Technically the AgreementProtocolAttributes can be set, but it has no effect on anax if a pattern is being used.
attr := api.NewAttribute("", "Global variables", false, false, map[string]interface{}{}) // we reuse this for each GlobalSet
if len(inputFileStruct.Global) > 0 {
msgPrinter.Printf("Setting global variables...")
msgPrinter.Println()
}
for _, g := range inputFileStruct.Global {
attr.Type = &g.Type
attr.ServiceSpecs = &g.ServiceSpecs
attr.Mappings = &g.Variables
// set HostOnly to true for these 2 types
switch g.Type {
case "HTTPSBasicAuthAttributes", "DockerRegistryAuthAttributes":
host_only := true
attr.HostOnly = &host_only
}
cliutils.HorizonPutPost(http.MethodPost, "attribute", []int{201, 200}, attr)
}
// Set the service variables
attr = api.NewAttribute("UserInputAttributes", "service", false, false, map[string]interface{}{}) // we reuse this for each service
emptyStr := ""
service := api.Service{Name: &emptyStr} // we reuse this too
if len(inputFileStruct.Services) > 0 {
msgPrinter.Printf("Setting service variables...")
msgPrinter.Println()
}
for _, m := range inputFileStruct.Services {
service.Org = &m.Org
service.Url = &m.Url
service.VersionRange = &m.VersionRange
attr.Mappings = &m.Variables
attrSlice := []api.Attribute{*attr}
service.Attributes = &attrSlice
httpCode, respBody := cliutils.HorizonPutPost(http.MethodPost, "service/config", []int{201, 200, 400}, service)
if httpCode == 400 {
if matches := parseRegisterInputError(respBody); matches != nil && len(matches) > 2 {
cliutils.Fatal(cliutils.CLI_INPUT_ERROR, msgPrinter.Sprintf("Registration failed because %v Please update the services section in the input file %v. Run 'hzn unregister' and then 'hzn register...' again", matches[0], inputFile))
}
cliutils.Fatal(cliutils.CLI_INPUT_ERROR, "Error setting service variables from user input file: %v", respBody)
}
}
} else {
// Technically an input file is not required, but it is not the common case, so warn them
msgPrinter.Printf("Warning: no input file was specified. This is only valid if none of the services need variables set (including GPS coordinates).")
msgPrinter.Println()
}
// Set the pattern and register the node
msgPrinter.Printf("Changing Horizon state to configured to register this node with Horizon...")
msgPrinter.Println()
configuredStr := "configured"
configState := api.Configstate{State: &configuredStr}
httpCode, respBody := cliutils.HorizonPutPost(http.MethodPut, "node/configstate", []int{201, 200, 400}, configState)
if httpCode == 400 {
if matches := parseRegisterInputError(respBody); matches != nil && len(matches) > 2 {
err_string := fmt.Sprintf("Registration failed because %v", matches[0])
if inputFile != "" {
cliutils.Fatal(cliutils.CLI_INPUT_ERROR, msgPrinter.Sprintf("%v. Please define variables for service %v in the input file %v. Run 'hzn unregister' and then 'hzn register...' again", err_string, matches[2], inputFile))
} else {
cliutils.Fatal(cliutils.CLI_INPUT_ERROR, msgPrinter.Sprintf("%v. Please create an input file, define variables for service %v. Run 'hzn unregister' and then 'hzn register...' again with the -f flag to specify the input file.", err_string, matches[2]))
}
}
cliutils.Fatal(cliutils.CLI_INPUT_ERROR, "%v", respBody)
}
msgPrinter.Printf("Horizon node is registered. Workload agreement negotiation should begin shortly. Run 'hzn agreement list' to view.")
msgPrinter.Println()
}
func verifyRegisterParamters(org, pattern, nodeOrgFromFlag string, patternFromFlag string) (string, string) {
// get message printer
msgPrinter := i18n.GetMessagePrinter()
if nodeOrgFromFlag != "" || patternFromFlag != "" {
if org != "" || pattern != "" {
cliutils.Fatal(cliutils.CLI_INPUT_ERROR, msgPrinter.Sprintf("-o and -p are mutually exclusive with <nodeorg> and <pattern> arguments."))
} else {
org = nodeOrgFromFlag
pattern = patternFromFlag
}
}
// get default org if needed
if org == "" {
org = os.Getenv("HZN_ORG_ID")
}
if org == "" {
cliutils.Fatal(cliutils.CLI_INPUT_ERROR, msgPrinter.Sprintf("Please specify the node organization id."))
}
return org, pattern
}
// isWithinRanges returns true if version is within at least 1 of the ranges in versionRanges
func isWithinRanges(version string, versionRanges []string) bool {
// get message printer
msgPrinter := i18n.GetMessagePrinter()
for _, vr := range versionRanges {
vRange, err := semanticversion.Version_Expression_Factory(vr)
if err != nil {
cliutils.Fatal(cliutils.CLI_GENERAL_ERROR, msgPrinter.Sprintf("invalid version range '%s': %v", vr, err))
}
if inRange, err := vRange.Is_within_range(version); err != nil {
cliutils.Fatal(cliutils.CLI_GENERAL_ERROR, msgPrinter.Sprintf("unable to verify that %v is within %v, error %v", version, vRange, err))
} else if inRange {
return true
}
}
return false // was not within any of the ranges
}
// GetHighestService queries the exchange for all versions of this service and returns the highest version that is within at least 1 of the version ranges
func GetHighestService(nodeCreds, org, url, arch string, versionRanges []string) exchange.ServiceDefinition {
// get message printer
msgPrinter := i18n.GetMessagePrinter()
route := "orgs/" + org + "/services?url=" + url + "&arch=" + arch // get all services of this org, url, and arch
var svcOutput exchange.GetServicesResponse
cliutils.SetWhetherUsingApiKey(nodeCreds)
cliutils.ExchangeGet("Exchange", cliutils.GetExchangeUrl(), route, nodeCreds, []int{200}, &svcOutput)
if len(svcOutput.Services) == 0 {
cliutils.Fatal(cliutils.CLI_GENERAL_ERROR, msgPrinter.Sprintf("found no services in the exchange matching: org=%s, url=%s, arch=%s", org, url, arch))
}
// Loop thru the returned services and pick out the highest version that is within one of the versionRanges
highestKey := "" // key to the service def in the map that so far has the highest valid version
for svcKey, svc := range svcOutput.Services {
if !isWithinRanges(svc.Version, versionRanges) {
continue // not within any of the specified version ranges, so ignore it
}
if highestKey == "" {
highestKey = svcKey // 1st svc found that is within the range
continue
}
// else see if this version is higher than the previous highest version
c, err := semanticversion.CompareVersions(svcOutput.Services[highestKey].Version, svc.Version)
if err != nil {
cliutils.Fatal(cliutils.CLI_GENERAL_ERROR, msgPrinter.Sprintf("error comparing version %v with version %v. %v", svcOutput.Services[highestKey], svc.Version, err))
} else if c == -1 {
highestKey = svcKey
}
}
if highestKey == "" {
cliutils.Fatal(cliutils.CLI_GENERAL_ERROR, msgPrinter.Sprintf("found no services in the exchange matched: org=%s, specRef=%s, version range=%s, arch=%s", org, url, versionRanges, arch))
}
return svcOutput.Services[highestKey]
}
func formSvcKey(org, url, arch string) string {
return org + "_" + url + "_" + arch
}
type SvcMapValue struct {
Org string
URL string
Arch string
VersionRanges []string // all the version ranges we find for this service as we descend thru the required services
HighestVersion string // filled in when we have to find the highest service to get its required services. Is valid at the end if len(VersionRanges)==1
UserInputs []exchange.UserInput
}
// AddAllRequiredSvcs
func AddAllRequiredSvcs(nodeCreds, org, url, arch, versionRange string, allRequiredSvcs map[string]*SvcMapValue) {
// get message printer
msgPrinter := i18n.GetMessagePrinter()
// Add this service to the service map
cliutils.Verbose(msgPrinter.Sprintf("found: %s, %s, %s, %s", org, url, arch, versionRange))
svcKey := formSvcKey(org, url, arch)
if s, ok := allRequiredSvcs[svcKey]; ok {
// To protect against circular service references, check if we've already seen this exact svc version range
for _, v := range s.VersionRanges {
if v == versionRange {
return
}
}
} else {
allRequiredSvcs[svcKey] = &SvcMapValue{Org: org, URL: url, Arch: arch} // this must be a ptr to the struct or go won't let us modify it in the map
}
allRequiredSvcs[svcKey].VersionRanges = append(allRequiredSvcs[svcKey].VersionRanges, versionRange) // add this version to this service in our map
// Get the service from the exchange so we can get its required services
highestSvc := GetHighestService(nodeCreds, org, url, arch, []string{versionRange})
allRequiredSvcs[svcKey].HighestVersion = highestSvc.Version // in case we don't encounter this service again, we already know the highest version for getting the user input from
allRequiredSvcs[svcKey].UserInputs = highestSvc.UserInputs
// Loop thru this service's required services, adding them to our map
for _, s := range highestSvc.RequiredServices {
// This will add this svc to our map and keep descending down the required services
AddAllRequiredSvcs(nodeCreds, s.Org, s.URL, s.Arch, s.Version, allRequiredSvcs)
}
}
// CreateInputFile runs thru the services used by this pattern (descending into all required services) and collects the user input needed
func CreateInputFile(nodeOrg, pattern, arch, nodeIdTok, inputFile string) {
// get message printer
msgPrinter := i18n.GetMessagePrinter()
var patOrg string
patOrg, pattern = cliutils.TrimOrg(nodeOrg, pattern) // patOrg will either get the prefix from pattern, or default to nodeOrg
nodeCreds := cliutils.OrgAndCreds(nodeOrg, nodeIdTok)
// Get the pattern
var patOutput exchange.GetPatternResponse
cliutils.ExchangeGet("Exchange", cliutils.GetExchangeUrl(), "orgs/"+patOrg+"/patterns/"+pattern, nodeCreds, []int{200}, &patOutput)
patKey := cliutils.OrgAndCreds(patOrg, pattern)
if _, ok := patOutput.Patterns[patKey]; !ok {
cliutils.Fatal(cliutils.INTERNAL_ERROR, msgPrinter.Sprintf("did not find pattern '%s' as expected", patKey))
}
if arch == "" {
arch = cutil.ArchString()
}
// Recursively go thru the services and their required services, collecting them in a map.
// Afterward we will process them to figure out the highest version of each before getting their input.
allRequiredSvcs := make(map[string]*SvcMapValue) // the key is the combined org, url, arch. The value is the org, url, arch and a list of the versions.
for _, svc := range patOutput.Patterns[patKey].Services {
if svc.ServiceArch != arch { // filter out services that are not our arch
msgPrinter.Printf("Ignoring service that is a different architecture: %s, %s, %s", svc.ServiceOrg, svc.ServiceURL, svc.ServiceArch)
msgPrinter.Println()
continue
}
for _, svcVersion := range svc.ServiceVersions {
// This will add this svc to our map and keep descending down the required services
AddAllRequiredSvcs(nodeCreds, svc.ServiceOrg, svc.ServiceURL, svc.ServiceArch, svcVersion.Version, allRequiredSvcs) // svcVersion.Version is a version range
}
}
// Loop thru each service, find the highest version of that service, and then record the user input for it
// Note: if the pattern references multiple versions of the same service (directly or indirectly), we create input for the highest version of the service.
templateFile := InputFile{Global: []GlobalSet{}}
for _, s := range allRequiredSvcs {
var userInput []exchange.UserInput
if s.HighestVersion != "" && len(s.VersionRanges) <= 1 {
// When we were finding the required services we only encountered this service once, so the user input we found then is valid
userInput = s.UserInputs
} else {
svc := GetHighestService(nodeCreds, s.Org, s.URL, s.Arch, s.VersionRanges)
userInput = svc.UserInputs
}
// Get the user input from this service
if len(userInput) > 0 {
svcInput := MicroWork{Org: s.Org, Url: s.URL, VersionRange: "[0.0.0,INFINITY)", Variables: make(map[string]interface{})}
for _, u := range userInput {
svcInput.Variables[u.Name] = u.DefaultValue
}
templateFile.Services = append(templateFile.Services, svcInput)
}
}
// Output the template file
jsonBytes, err := json.MarshalIndent(templateFile, "", cliutils.JSON_INDENT)
if err != nil {
cliutils.Fatal(cliutils.INTERNAL_ERROR, msgPrinter.Sprintf("failed to marshal the user input template file: %v", err))
}
if err := ioutil.WriteFile(inputFile, jsonBytes, 0644); err != nil {
cliutils.Fatal(cliutils.FILE_IO_ERROR, msgPrinter.Sprintf("problem writing the user input template file: %v", err))
}
msgPrinter.Printf("Wrote %s", inputFile)
msgPrinter.Println()
}
// this function parses the error returned by the registration process to see if the error
// is due to the missing input variable or wrong input variable type for a service.
// It returns a string array in the following format:
// [error, variable name, service org/service url]
// if it returns an empty array, then there is no match.
func parseRegisterInputError(resp string) []string {
// match the ANAX_SVC_MISSING_VARIABLE
tmplt_miss_var := strings.Replace(cutil.ANAX_SVC_MISSING_VARIABLE, "%v", "([^\\s]*)", -1)
re1 := regexp.MustCompile(tmplt_miss_var)
matches := re1.FindStringSubmatch(resp)
// match ANAX_SVC_WRONG_TYPE
if matches == nil || len(matches) == 0 {
tmplt_wrong_type := strings.Replace(cutil.ANAX_SVC_WRONG_TYPE, "%v", "([^\\s]*)", -1) + "type [^.]*[.]"
re2 := regexp.MustCompile(tmplt_wrong_type)
matches = re2.FindStringSubmatch(resp)
}
// match ANAX_SVC_MISSING_CONFIG
if matches == nil || len(matches) == 0 {
tmplt_missing_config := strings.Replace(cutil.ANAX_SVC_MISSING_CONFIG, "%v", "([^\\s]*)", -1)
re3 := regexp.MustCompile(tmplt_missing_config)
matches = re3.FindStringSubmatch(resp)
if matches != nil && len(matches) > 2 {
// no variable name for this
matches[1] = ""
}
}
return matches
}