forked from Azure/acs-engine
/
validate.go
657 lines (589 loc) · 24 KB
/
validate.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
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
package vlabs
import (
"errors"
"fmt"
"net"
"net/url"
"regexp"
"strings"
"time"
"github.com/Azure/acs-engine/pkg/api/common"
"github.com/satori/uuid"
validator "gopkg.in/go-playground/validator.v9"
)
var (
validate *validator.Validate
keyvaultIDRegex *regexp.Regexp
)
func init() {
validate = validator.New()
keyvaultIDRegex = regexp.MustCompile(`^/subscriptions/\S+/resourceGroups/\S+/providers/Microsoft.KeyVault/vaults/[^/\s]+$`)
}
// Validate implements APIObject
func (o *OrchestratorProfile) Validate() error {
// Don't need to call validate.Struct(o)
// It is handled by Properties.Validate()
switch o.OrchestratorType {
case DCOS:
switch o.OrchestratorRelease {
case common.DCOSRelease1Dot8:
case common.DCOSRelease1Dot9:
case common.DCOSRelease1Dot10:
case "":
default:
return fmt.Errorf("OrchestratorProfile has unknown orchestrator release: %s", o.OrchestratorRelease)
}
case Swarm:
case SwarmMode:
case Kubernetes:
switch o.OrchestratorRelease {
case common.KubernetesRelease1Dot8:
case common.KubernetesRelease1Dot7:
case common.KubernetesRelease1Dot6:
case common.KubernetesRelease1Dot5:
case "":
default:
return fmt.Errorf("OrchestratorProfile has unknown orchestrator release: %s", o.OrchestratorRelease)
}
if o.KubernetesConfig != nil {
err := o.KubernetesConfig.Validate(o.OrchestratorRelease)
if err != nil {
return err
}
if o.KubernetesConfig.EnableAggregatedAPIs {
if o.OrchestratorRelease == common.KubernetesRelease1Dot5 || o.OrchestratorRelease == common.KubernetesRelease1Dot6 {
return fmt.Errorf("enableAggregatedAPIs is only available in Kubernetes version %s or greater; unable to validate for Kubernetes version %s",
common.KubernetesRelease1Dot7, o.OrchestratorRelease)
}
if !o.KubernetesConfig.EnableRbac {
return fmt.Errorf("enableAggregatedAPIs requires the enableRbac feature as a prerequisite")
}
}
}
default:
return fmt.Errorf("OrchestratorProfile has unknown orchestrator: %s", o.OrchestratorType)
}
if o.OrchestratorType != Kubernetes && o.KubernetesConfig != nil && (*o.KubernetesConfig != KubernetesConfig{}) {
return fmt.Errorf("KubernetesConfig can be specified only when OrchestratorType is Kubernetes")
}
if o.OrchestratorType != DCOS && o.DcosConfig != nil && (*o.DcosConfig != DcosConfig{}) {
return fmt.Errorf("DcosConfig can be specified only when OrchestratorType is DCOS")
}
return nil
}
// Validate implements APIObject
func (m *MasterProfile) Validate() error {
if e := validateDNSName(m.DNSPrefix); e != nil {
return e
}
return nil
}
// Validate implements APIObject
func (a *AgentPoolProfile) Validate(orchestratorType string) error {
// Don't need to call validate.Struct(a)
// It is handled by Properties.Validate()
if e := validatePoolName(a.Name); e != nil {
return e
}
// for Kubernetes, we don't support AgentPoolProfile.DNSPrefix
if orchestratorType == Kubernetes {
if e := validate.Var(a.DNSPrefix, "len=0"); e != nil {
return fmt.Errorf("AgentPoolProfile.DNSPrefix must be empty for Kubernetes")
}
if e := validate.Var(a.Ports, "len=0"); e != nil {
return fmt.Errorf("AgentPoolProfile.Ports must be empty for Kubernetes")
}
}
if a.DNSPrefix != "" {
if e := validateDNSName(a.DNSPrefix); e != nil {
return e
}
if len(a.Ports) > 0 {
if e := validateUniquePorts(a.Ports, a.Name); e != nil {
return e
}
} else {
a.Ports = []int{80, 443, 8080}
}
} else {
if e := validate.Var(a.Ports, "len=0"); e != nil {
return fmt.Errorf("AgentPoolProfile.Ports must be empty when AgentPoolProfile.DNSPrefix is empty for Orchestrator: %s", string(orchestratorType))
}
}
if len(a.DiskSizesGB) > 0 {
if e := validate.Var(a.StorageProfile, "eq=StorageAccount|eq=ManagedDisks"); e != nil {
return fmt.Errorf("property 'StorageProfile' must be set to either '%s' or '%s' when attaching disks", StorageAccount, ManagedDisks)
}
if e := validate.Var(a.AvailabilityProfile, "eq=VirtualMachineScaleSets|eq=AvailabilitySet"); e != nil {
return fmt.Errorf("property 'AvailabilityProfile' must be set to either '%s' or '%s' when attaching disks", VirtualMachineScaleSets, AvailabilitySet)
}
if a.StorageProfile == StorageAccount && (a.AvailabilityProfile == VirtualMachineScaleSets) {
return fmt.Errorf("VirtualMachineScaleSets does not support storage account attached disks. Instead specify 'StorageAccount': '%s' or specify AvailabilityProfile '%s'", ManagedDisks, AvailabilitySet)
}
}
if len(a.Ports) == 0 && len(a.DNSPrefix) > 0 {
return fmt.Errorf("AgentPoolProfile.Ports must be non empty when AgentPoolProfile.DNSPrefix is specified")
}
return nil
}
// Validate implements APIObject
func (o *OrchestratorVersionProfile) Validate() error {
switch {
case strings.EqualFold(o.OrchestratorType, Kubernetes):
o.OrchestratorType = Kubernetes
if _, ok := common.KubeReleaseToVersion[o.OrchestratorRelease]; !ok {
return fmt.Errorf("Unsupported Kubernetes release '%s'", o.OrchestratorRelease)
}
case strings.EqualFold(o.OrchestratorType, DCOS):
o.OrchestratorType = DCOS
if _, ok := common.DCOSReleaseToVersion[o.OrchestratorRelease]; !ok {
return fmt.Errorf("Unsupported DCOS release '%s'", o.OrchestratorRelease)
}
case strings.EqualFold(o.OrchestratorType, Swarm):
o.OrchestratorType = Swarm
case strings.EqualFold(o.OrchestratorType, SwarmMode):
o.OrchestratorType = SwarmMode
default:
return fmt.Errorf("Unsupported orchestrator '%s'", o.OrchestratorType)
}
return nil
}
// ValidateForUpgrade validates upgrade input data
func (o *OrchestratorProfile) ValidateForUpgrade() error {
switch o.OrchestratorType {
case DCOS, SwarmMode, Swarm:
return fmt.Errorf("Upgrade is not supported for orchestrator %s", o.OrchestratorType)
case Kubernetes:
switch o.OrchestratorRelease {
case common.KubernetesRelease1Dot6:
case common.KubernetesRelease1Dot7:
default:
return fmt.Errorf("Upgrade to Kubernetes %s is not supported", o.OrchestratorRelease)
}
}
return nil
}
func validateKeyVaultSecrets(secrets []KeyVaultSecrets, requireCertificateStore bool) error {
for _, s := range secrets {
if len(s.VaultCertificates) == 0 {
return fmt.Errorf("Invalid KeyVaultSecrets must have no empty VaultCertificates")
}
if s.SourceVault == nil {
return fmt.Errorf("missing SourceVault in KeyVaultSecrets")
}
if s.SourceVault.ID == "" {
return fmt.Errorf("KeyVaultSecrets must have a SourceVault.ID")
}
for _, c := range s.VaultCertificates {
if _, e := url.Parse(c.CertificateURL); e != nil {
return fmt.Errorf("Certificate url was invalid. received error %s", e)
}
if e := validateName(c.CertificateStore, "KeyVaultCertificate.CertificateStore"); requireCertificateStore && e != nil {
return fmt.Errorf("%s for certificates in a WindowsProfile", e)
}
}
}
return nil
}
// Validate implements APIObject
func (l *LinuxProfile) Validate() error {
// Don't need to call validate.Struct(l)
// It is handled by Properties.Validate()
if e := validate.Var(l.SSH.PublicKeys[0].KeyData, "required"); e != nil {
return fmt.Errorf("KeyData in LinuxProfile.SSH.PublicKeys cannot be empty string")
}
if e := validateKeyVaultSecrets(l.Secrets, false); e != nil {
return e
}
return nil
}
func handleValidationErrors(e validator.ValidationErrors) error {
// Override any version specific validation error message
// common.HandleValidationErrors if the validation error message is general
return common.HandleValidationErrors(e)
}
// Validate implements APIObject
func (profile *AADProfile) Validate() error {
if _, err := uuid.FromString(profile.ClientAppID); err != nil {
return fmt.Errorf("clientAppID '%v' is invalid", profile.ClientAppID)
}
if _, err := uuid.FromString(profile.ServerAppID); err != nil {
return fmt.Errorf("serverAppID '%v' is invalid", profile.ServerAppID)
}
if len(profile.TenantID) > 0 {
if _, err := uuid.FromString(profile.TenantID); err != nil {
return fmt.Errorf("tenantID '%v' is invalid", profile.TenantID)
}
}
return nil
}
// Validate implements APIObject
func (a *Properties) Validate() error {
if e := validate.Struct(a); e != nil {
return handleValidationErrors(e.(validator.ValidationErrors))
}
if e := a.OrchestratorProfile.Validate(); e != nil {
return e
}
if e := a.validateNetworkPolicy(); e != nil {
return e
}
if e := a.MasterProfile.Validate(); e != nil {
return e
}
if e := validateUniqueProfileNames(a.AgentPoolProfiles); e != nil {
return e
}
if a.OrchestratorProfile.OrchestratorType == Kubernetes {
useManagedIdentity := (a.OrchestratorProfile.KubernetesConfig != nil &&
a.OrchestratorProfile.KubernetesConfig.UseManagedIdentity)
if !useManagedIdentity {
if a.ServicePrincipalProfile == nil {
return fmt.Errorf("ServicePrincipalProfile must be specified with Orchestrator %s", a.OrchestratorProfile.OrchestratorType)
}
if e := validate.Var(a.ServicePrincipalProfile.ClientID, "required"); e != nil {
return fmt.Errorf("the service principal client ID must be specified with Orchestrator %s", a.OrchestratorProfile.OrchestratorType)
}
if (len(a.ServicePrincipalProfile.Secret) == 0 && a.ServicePrincipalProfile.KeyvaultSecretRef == nil) ||
(len(a.ServicePrincipalProfile.Secret) != 0 && a.ServicePrincipalProfile.KeyvaultSecretRef != nil) {
return fmt.Errorf("either the service principal client secret or keyvault secret reference must be specified with Orchestrator %s", a.OrchestratorProfile.OrchestratorType)
}
if a.ServicePrincipalProfile.KeyvaultSecretRef != nil {
if e := validate.Var(a.ServicePrincipalProfile.KeyvaultSecretRef.VaultID, "required"); e != nil {
return fmt.Errorf("the Keyvault ID must be specified for the Service Principle with Orchestrator %s", a.OrchestratorProfile.OrchestratorType)
}
if e := validate.Var(a.ServicePrincipalProfile.KeyvaultSecretRef.SecretName, "required"); e != nil {
return fmt.Errorf("the Keyvault Secret must be specified for the Service Principle with Orchestrator %s", a.OrchestratorProfile.OrchestratorType)
}
if !keyvaultIDRegex.MatchString(a.ServicePrincipalProfile.KeyvaultSecretRef.VaultID) {
return fmt.Errorf("service principal client keyvault secret reference is of incorrect format")
}
}
}
}
for _, agentPoolProfile := range a.AgentPoolProfiles {
if e := agentPoolProfile.Validate(a.OrchestratorProfile.OrchestratorType); e != nil {
return e
}
switch agentPoolProfile.AvailabilityProfile {
case AvailabilitySet:
case VirtualMachineScaleSets:
case "":
default:
{
return fmt.Errorf("unknown availability profile type '%s' for agent pool '%s'. Specify either %s, or %s", agentPoolProfile.AvailabilityProfile, agentPoolProfile.Name, AvailabilitySet, VirtualMachineScaleSets)
}
}
/* this switch statement is left to protect newly added orchestrators until they support Managed Disks*/
if agentPoolProfile.StorageProfile == ManagedDisks {
switch a.OrchestratorProfile.OrchestratorType {
case DCOS:
case Swarm:
case Kubernetes:
case SwarmMode:
default:
return fmt.Errorf("HA volumes are currently unsupported for Orchestrator %s", a.OrchestratorProfile.OrchestratorType)
}
}
if len(agentPoolProfile.CustomNodeLabels) > 0 {
switch a.OrchestratorProfile.OrchestratorType {
case DCOS:
case Kubernetes:
default:
return fmt.Errorf("Agent Type attributes are only supported for DCOS and Kubernetes")
}
}
if a.OrchestratorProfile.OrchestratorType == Kubernetes && (agentPoolProfile.AvailabilityProfile == VirtualMachineScaleSets || len(agentPoolProfile.AvailabilityProfile) == 0) {
return fmt.Errorf("VirtualMachineScaleSets are not supported with Kubernetes since Kubernetes requires the ability to attach/detach disks. To fix specify \"AvailabilityProfile\":\"%s\"", AvailabilitySet)
}
if agentPoolProfile.OSType == Windows {
if e := validate.Var(a.WindowsProfile, "required"); e != nil {
return fmt.Errorf("WindowsProfile must not be empty since agent pool '%s' specifies windows", agentPoolProfile.Name)
}
switch a.OrchestratorProfile.OrchestratorType {
case DCOS:
case Swarm:
case SwarmMode:
case Kubernetes:
default:
return fmt.Errorf("Orchestrator %s does not support Windows", a.OrchestratorProfile.OrchestratorType)
}
if e := validate.Var(a.WindowsProfile.AdminUsername, "required"); e != nil {
return fmt.Errorf("WindowsProfile.AdminUsername is required, when agent pool specifies windows")
}
if e := validate.Var(a.WindowsProfile.AdminPassword, "required"); e != nil {
return fmt.Errorf("WindowsProfile.AdminPassword is required, when agent pool specifies windows")
}
if e := validateKeyVaultSecrets(a.WindowsProfile.Secrets, true); e != nil {
return e
}
}
}
if e := a.LinuxProfile.Validate(); e != nil {
return e
}
if e := validateVNET(a); e != nil {
return e
}
if a.AADProfile != nil {
if a.OrchestratorProfile.OrchestratorType != Kubernetes {
return fmt.Errorf("'aadProfile' is only supported by orchestrator '%v'", Kubernetes)
}
if e := a.AADProfile.Validate(); e != nil {
return e
}
}
for _, extension := range a.ExtensionProfiles {
if extension.ExtensionParametersKeyVaultRef != nil {
if e := validate.Var(extension.ExtensionParametersKeyVaultRef.VaultID, "required"); e != nil {
return fmt.Errorf("the Keyvault ID must be specified for Extension %s", extension.Name)
}
if e := validate.Var(extension.ExtensionParametersKeyVaultRef.SecretName, "required"); e != nil {
return fmt.Errorf("the Keyvault Secret must be specified for Extension %s", extension.Name)
}
if !keyvaultIDRegex.MatchString(extension.ExtensionParametersKeyVaultRef.VaultID) {
return fmt.Errorf("Extension %s's keyvault secret reference is of incorrect format", extension.Name)
}
}
}
return nil
}
// Validate validates the KubernetesConfig.
func (a *KubernetesConfig) Validate(k8sRelease string) error {
// number of minimum retries allowed for kubelet to post node status
const minKubeletRetries = 4
// k8s releases that have cloudprovider backoff enabled
var backoffEnabledReleases = map[string]bool{
common.KubernetesRelease1Dot8: true,
common.KubernetesRelease1Dot7: true,
common.KubernetesRelease1Dot6: true,
}
// k8s releases that have cloudprovider rate limiting enabled (currently identical with backoff enabled releases)
ratelimitEnabledReleases := backoffEnabledReleases
if a.ClusterSubnet != "" {
_, subnet, err := net.ParseCIDR(a.ClusterSubnet)
if err != nil {
return fmt.Errorf("OrchestratorProfile.KubernetesConfig.ClusterSubnet '%s' is an invalid subnet", a.ClusterSubnet)
}
ones, bits := subnet.Mask.Size()
if bits-ones <= 8 {
return fmt.Errorf("OrchestratorProfile.KubernetesConfig.ClusterSubnet '%s' must reserve at least 9 bits for nodes", a.ClusterSubnet)
}
}
if a.DockerBridgeSubnet != "" {
_, _, err := net.ParseCIDR(a.DockerBridgeSubnet)
if err != nil {
return fmt.Errorf("OrchestratorProfile.KubernetesConfig.DockerBridgeSubnet '%s' is an invalid subnet", a.DockerBridgeSubnet)
}
}
if a.MaxPods != 0 {
if a.MaxPods < KubernetesMinMaxPods {
return fmt.Errorf("OrchestratorProfile.KubernetesConfig.MaxPods '%v' must be at least %v", a.MaxPods, KubernetesMinMaxPods)
}
}
if a.NodeStatusUpdateFrequency != "" {
_, err := time.ParseDuration(a.NodeStatusUpdateFrequency)
if err != nil {
return fmt.Errorf("OrchestratorProfile.KubernetesConfig.NodeStatusUpdateFrequency '%s' is not a valid duration", a.NodeStatusUpdateFrequency)
}
if a.CtrlMgrNodeMonitorGracePeriod == "" {
return fmt.Errorf("OrchestratorProfile.KubernetesConfig.NodeStatusUpdateFrequency was set to '%s' but OrchestratorProfile.KubernetesConfig.CtrlMgrNodeMonitorGracePeriod was not set", a.NodeStatusUpdateFrequency)
}
}
if a.CtrlMgrNodeMonitorGracePeriod != "" {
_, err := time.ParseDuration(a.CtrlMgrNodeMonitorGracePeriod)
if err != nil {
return fmt.Errorf("OrchestratorProfile.KubernetesConfig.CtrlMgrNodeMonitorGracePeriod '%s' is not a valid duration", a.CtrlMgrNodeMonitorGracePeriod)
}
if a.NodeStatusUpdateFrequency == "" {
return fmt.Errorf("OrchestratorProfile.KubernetesConfig.CtrlMgrNodeMonitorGracePeriod was set to '%s' but OrchestratorProfile.KubernetesConfig.NodeStatusUpdateFrequency was not set", a.NodeStatusUpdateFrequency)
}
}
if a.NodeStatusUpdateFrequency != "" && a.CtrlMgrNodeMonitorGracePeriod != "" {
nodeStatusUpdateFrequency, _ := time.ParseDuration(a.NodeStatusUpdateFrequency)
ctrlMgrNodeMonitorGracePeriod, _ := time.ParseDuration(a.CtrlMgrNodeMonitorGracePeriod)
kubeletRetries := ctrlMgrNodeMonitorGracePeriod.Seconds() / nodeStatusUpdateFrequency.Seconds()
if kubeletRetries < minKubeletRetries {
return fmt.Errorf("acs-engine requires that ctrlMgrNodeMonitorGracePeriod(%f)s be larger than nodeStatusUpdateFrequency(%f)s by at least a factor of %d; ", ctrlMgrNodeMonitorGracePeriod.Seconds(), nodeStatusUpdateFrequency.Seconds(), minKubeletRetries)
}
}
if a.CtrlMgrPodEvictionTimeout != "" {
_, err := time.ParseDuration(a.CtrlMgrPodEvictionTimeout)
if err != nil {
return fmt.Errorf("OrchestratorProfile.KubernetesConfig.CtrlMgrPodEvictionTimeout '%s' is not a valid duration", a.CtrlMgrPodEvictionTimeout)
}
}
if a.CtrlMgrRouteReconciliationPeriod != "" {
_, err := time.ParseDuration(a.CtrlMgrRouteReconciliationPeriod)
if err != nil {
return fmt.Errorf("OrchestratorProfile.KubernetesConfig.CtrlMgrRouteReconciliationPeriod '%s' is not a valid duration", a.CtrlMgrRouteReconciliationPeriod)
}
}
if a.CloudProviderBackoff {
if !backoffEnabledReleases[k8sRelease] {
return fmt.Errorf("cloudprovider backoff functionality not available in kubernetes release %s", k8sRelease)
}
}
if a.CloudProviderRateLimit {
if !ratelimitEnabledReleases[k8sRelease] {
return fmt.Errorf("cloudprovider rate limiting functionality not available in kubernetes release %s", k8sRelease)
}
}
if a.DNSServiceIP != "" || a.ServiceCidr != "" {
if a.DNSServiceIP == "" {
return errors.New("OrchestratorProfile.KubernetesConfig.ServiceCidr must be specified when DNSServiceIP is")
}
if a.ServiceCidr == "" {
return errors.New("OrchestratorProfile.KubernetesConfig.DNSServiceIP must be specified when ServiceCidr is")
}
dnsIP := net.ParseIP(a.DNSServiceIP)
if dnsIP == nil {
return fmt.Errorf("OrchestratorProfile.KubernetesConfig.DNSServiceIP '%s' is an invalid IP address", a.DNSServiceIP)
}
_, serviceCidr, err := net.ParseCIDR(a.ServiceCidr)
if err != nil {
return fmt.Errorf("OrchestratorProfile.KubernetesConfig.ServiceCidr '%s' is an invalid CIDR subnet", a.ServiceCidr)
}
// Finally validate that the DNS ip is within the subnet
if !serviceCidr.Contains(dnsIP) {
return fmt.Errorf("OrchestratorProfile.KubernetesConfig.DNSServiceIP '%s' is not within the ServiceCidr '%s'", a.DNSServiceIP, a.ServiceCidr)
}
// and that the DNS IP is _not_ the subnet broadcast address
broadcast := common.IP4BroadcastAddress(serviceCidr)
if dnsIP.Equal(broadcast) {
return fmt.Errorf("OrchestratorProfile.KubernetesConfig.DNSServiceIP '%s' cannot be the broadcast address of ServiceCidr '%s'", a.DNSServiceIP, a.ServiceCidr)
}
// and that the DNS IP is _not_ the first IP in the service subnet
firstServiceIP := common.CidrFirstIP(serviceCidr.IP)
if firstServiceIP.Equal(dnsIP) {
return fmt.Errorf("OrchestratorProfile.KubernetesConfig.DNSServiceIP '%s' cannot be the first IP of ServiceCidr '%s'", a.DNSServiceIP, a.ServiceCidr)
}
}
return nil
}
func (a *Properties) validateNetworkPolicy() error {
var networkPolicy string
switch a.OrchestratorProfile.OrchestratorType {
case Kubernetes:
if a.OrchestratorProfile.KubernetesConfig != nil {
networkPolicy = a.OrchestratorProfile.KubernetesConfig.NetworkPolicy
}
default:
return nil
}
// Check NetworkPolicy has a valid value.
valid := false
for _, policy := range NetworkPolicyValues {
if networkPolicy == policy {
valid = true
break
}
}
if !valid {
return fmt.Errorf("unknown networkPolicy '%s' specified", networkPolicy)
}
// Temporary safety check, to be removed when Windows support is added.
if (networkPolicy == "calico" || networkPolicy == "azure") && a.HasWindows() {
return fmt.Errorf("networkPolicy '%s' is not supporting windows agents", networkPolicy)
}
return nil
}
func validateName(name string, label string) error {
if name == "" {
return fmt.Errorf("%s must be a non-empty value", label)
}
return nil
}
func validatePoolName(poolName string) error {
// we will cap at length of 12 and all lowercase letters since this makes up the VMName
poolNameRegex := `^([a-z][a-z0-9]{0,11})$`
re, err := regexp.Compile(poolNameRegex)
if err != nil {
return err
}
submatches := re.FindStringSubmatch(poolName)
if len(submatches) != 2 {
return fmt.Errorf("pool name '%s' is invalid. A pool name must start with a lowercase letter, have max length of 12, and only have characters a-z0-9", poolName)
}
return nil
}
func validateDNSName(dnsName string) error {
dnsNameRegex := `^([A-Za-z][A-Za-z0-9-]{1,43}[A-Za-z0-9])$`
re, err := regexp.Compile(dnsNameRegex)
if err != nil {
return err
}
if !re.MatchString(dnsName) {
return fmt.Errorf("DNS name '%s' is invalid. The DNS name must contain between 3 and 45 characters. The name can contain only letters, numbers, and hyphens. The name must start with a letter and must end with a letter or a number (length was %d)", dnsName, len(dnsName))
}
return nil
}
func validateUniqueProfileNames(profiles []*AgentPoolProfile) error {
profileNames := make(map[string]bool)
for _, profile := range profiles {
if _, ok := profileNames[profile.Name]; ok {
return fmt.Errorf("profile name '%s' already exists, profile names must be unique across pools", profile.Name)
}
profileNames[profile.Name] = true
}
return nil
}
func validateUniquePorts(ports []int, name string) error {
portMap := make(map[int]bool)
for _, port := range ports {
if _, ok := portMap[port]; ok {
return fmt.Errorf("agent profile '%s' has duplicate port '%d', ports must be unique", name, port)
}
portMap[port] = true
}
return nil
}
func validateVNET(a *Properties) error {
isCustomVNET := a.MasterProfile.IsCustomVNET()
for _, agentPool := range a.AgentPoolProfiles {
if agentPool.IsCustomVNET() != isCustomVNET {
return fmt.Errorf("Multiple VNET Subnet configurations specified. The master profile and each agent pool profile must all specify a custom VNET Subnet, or none at all")
}
}
if isCustomVNET {
subscription, resourcegroup, vnetname, _, e := GetVNETSubnetIDComponents(a.MasterProfile.VnetSubnetID)
if e != nil {
return e
}
for _, agentPool := range a.AgentPoolProfiles {
agentSubID, agentRG, agentVNET, _, err := GetVNETSubnetIDComponents(agentPool.VnetSubnetID)
if err != nil {
return err
}
if agentSubID != subscription ||
agentRG != resourcegroup ||
agentVNET != vnetname {
return errors.New("Multiple VNETS specified. The master profile and each agent pool must reference the same VNET (but it is ok to reference different subnets on that VNET)")
}
}
masterFirstIP := net.ParseIP(a.MasterProfile.FirstConsecutiveStaticIP)
if masterFirstIP == nil {
return fmt.Errorf("MasterProfile.FirstConsecutiveStaticIP (with VNET Subnet specification) '%s' is an invalid IP address", a.MasterProfile.FirstConsecutiveStaticIP)
}
if a.MasterProfile.VnetCidr != "" {
_, _, err := net.ParseCIDR(a.MasterProfile.VnetCidr)
if err != nil {
return fmt.Errorf("MasterProfile.VnetCidr '%s' contains invalid cidr notation", a.MasterProfile.VnetCidr)
}
}
}
return nil
}
// GetVNETSubnetIDComponents extract subscription, resourcegroup, vnetname, subnetname from the vnetSubnetID
func GetVNETSubnetIDComponents(vnetSubnetID string) (string, string, string, string, error) {
vnetSubnetIDRegex := `^\/subscriptions\/([^\/]*)\/resourceGroups\/([^\/]*)\/providers\/Microsoft.Network\/virtualNetworks\/([^\/]*)\/subnets\/([^\/]*)$`
re, err := regexp.Compile(vnetSubnetIDRegex)
if err != nil {
return "", "", "", "", err
}
submatches := re.FindStringSubmatch(vnetSubnetID)
if len(submatches) != 4 {
return "", "", "", "", err
}
return submatches[1], submatches[2], submatches[3], submatches[4], nil
}