PageRenderTime 70ms CodeModel.GetById 41ms RepoModel.GetById 0ms app.codeStats 1ms

/pkg/api/vlabs/validate.go

https://bitbucket.org/afawkes/acs-engine
Go | 1156 lines | 995 code | 112 blank | 49 comment | 505 complexity | 84ce9fadd4d36873f5f1c9d97d726b62 MD5 | raw file
Possible License(s): MIT, Apache-2.0, 0BSD, AGPL-3.0, BSD-3-Clause, LGPL-3.0
  1. package vlabs
  2. import (
  3. "encoding/base64"
  4. "errors"
  5. "fmt"
  6. "net"
  7. "net/url"
  8. "regexp"
  9. "strings"
  10. "time"
  11. "github.com/Azure/acs-engine/pkg/api/common"
  12. "github.com/Azure/acs-engine/pkg/helpers"
  13. "github.com/Masterminds/semver"
  14. "github.com/satori/uuid"
  15. validator "gopkg.in/go-playground/validator.v9"
  16. )
  17. var (
  18. validate *validator.Validate
  19. keyvaultIDRegex *regexp.Regexp
  20. labelValueRegex *regexp.Regexp
  21. labelKeyRegex *regexp.Regexp
  22. // Any version has to be mirrored in https://acs-mirror.azureedge.net/github-coreos/etcd-v[Version]-linux-amd64.tar.gz
  23. etcdValidVersions = [...]string{"2.2.5", "2.3.0", "2.3.1", "2.3.2", "2.3.3", "2.3.4", "2.3.5", "2.3.6", "2.3.7", "2.3.8",
  24. "3.0.0", "3.0.1", "3.0.2", "3.0.3", "3.0.4", "3.0.5", "3.0.6", "3.0.7", "3.0.8", "3.0.9", "3.0.10", "3.0.11", "3.0.12", "3.0.13", "3.0.14", "3.0.15", "3.0.16", "3.0.17",
  25. "3.1.0", "3.1.1", "3.1.2", "3.1.2", "3.1.3", "3.1.4", "3.1.5", "3.1.6", "3.1.7", "3.1.8", "3.1.9", "3.1.10",
  26. "3.2.0", "3.2.1", "3.2.2", "3.2.3", "3.2.4", "3.2.5", "3.2.6", "3.2.7", "3.2.8", "3.2.9", "3.2.11", "3.2.12",
  27. "3.2.13", "3.2.14", "3.2.15", "3.2.16", "3.3.0", "3.3.1"}
  28. networkPluginPlusPolicyAllowed = []k8sNetworkConfig{
  29. {
  30. networkPlugin: "",
  31. networkPolicy: "",
  32. },
  33. {
  34. networkPlugin: "azure",
  35. networkPolicy: "",
  36. },
  37. {
  38. networkPlugin: "kubenet",
  39. networkPolicy: "",
  40. },
  41. {
  42. networkPlugin: "flannel",
  43. networkPolicy: "",
  44. },
  45. {
  46. networkPlugin: "cilium",
  47. networkPolicy: "",
  48. },
  49. {
  50. networkPlugin: "cilium",
  51. networkPolicy: "cilium",
  52. },
  53. {
  54. networkPlugin: "kubenet",
  55. networkPolicy: "calico",
  56. },
  57. {
  58. networkPlugin: "",
  59. networkPolicy: "calico",
  60. },
  61. {
  62. networkPlugin: "",
  63. networkPolicy: "cilium",
  64. },
  65. {
  66. networkPlugin: "",
  67. networkPolicy: "azure", // for backwards-compatibility w/ prior networkPolicy usage
  68. },
  69. {
  70. networkPlugin: "",
  71. networkPolicy: "none", // for backwards-compatibility w/ prior networkPolicy usage
  72. },
  73. }
  74. )
  75. const (
  76. labelKeyPrefixMaxLength = 253
  77. labelValueFormat = "^([A-Za-z0-9][-A-Za-z0-9_.]{0,61})?[A-Za-z0-9]$"
  78. labelKeyFormat = "^(([a-zA-Z0-9-]+[.])*[a-zA-Z0-9-]+[/])?([A-Za-z0-9][-A-Za-z0-9_.]{0,61})?[A-Za-z0-9]$"
  79. )
  80. type k8sNetworkConfig struct {
  81. networkPlugin string
  82. networkPolicy string
  83. }
  84. func init() {
  85. validate = validator.New()
  86. keyvaultIDRegex = regexp.MustCompile(`^/subscriptions/\S+/resourceGroups/\S+/providers/Microsoft.KeyVault/vaults/[^/\s]+$`)
  87. labelValueRegex = regexp.MustCompile(labelValueFormat)
  88. labelKeyRegex = regexp.MustCompile(labelKeyFormat)
  89. }
  90. func isValidEtcdVersion(etcdVersion string) error {
  91. // "" is a valid etcdVersion that maps to DefaultEtcdVersion
  92. if etcdVersion == "" {
  93. return nil
  94. }
  95. for _, ver := range etcdValidVersions {
  96. if ver == etcdVersion {
  97. return nil
  98. }
  99. }
  100. return fmt.Errorf("Invalid etcd version(%s), valid versions are%s", etcdVersion, etcdValidVersions)
  101. }
  102. // Validate implements APIObject
  103. func (o *OrchestratorProfile) Validate(isUpdate bool) error {
  104. // Don't need to call validate.Struct(o)
  105. // It is handled by Properties.Validate()
  106. // On updates we only need to make sure there is a supported patch version for the minor version
  107. if !isUpdate {
  108. switch o.OrchestratorType {
  109. case DCOS:
  110. version := common.RationalizeReleaseAndVersion(
  111. o.OrchestratorType,
  112. o.OrchestratorRelease,
  113. o.OrchestratorVersion,
  114. false)
  115. if version == "" {
  116. return fmt.Errorf("the following user supplied OrchestratorProfile configuration is not supported: OrchestratorType: %s, OrchestratorRelease: %s, OrchestratorVersion: %s. Please check supported Release or Version for this build of acs-engine", o.OrchestratorType, o.OrchestratorRelease, o.OrchestratorVersion)
  117. }
  118. if o.DcosConfig != nil && o.DcosConfig.BootstrapProfile != nil {
  119. if len(o.DcosConfig.BootstrapProfile.StaticIP) > 0 {
  120. if net.ParseIP(o.DcosConfig.BootstrapProfile.StaticIP) == nil {
  121. return fmt.Errorf("DcosConfig.BootstrapProfile.StaticIP '%s' is an invalid IP address",
  122. o.DcosConfig.BootstrapProfile.StaticIP)
  123. }
  124. }
  125. }
  126. case Swarm:
  127. case SwarmMode:
  128. case Kubernetes:
  129. version := common.RationalizeReleaseAndVersion(
  130. o.OrchestratorType,
  131. o.OrchestratorRelease,
  132. o.OrchestratorVersion,
  133. false)
  134. if version == "" {
  135. return fmt.Errorf("the following user supplied OrchestratorProfile configuration is not supported: OrchestratorType: %s, OrchestratorRelease: %s, OrchestratorVersion: %s. Please check supported Release or Version for this build of acs-engine", o.OrchestratorType, o.OrchestratorRelease, o.OrchestratorVersion)
  136. }
  137. if o.KubernetesConfig != nil {
  138. err := o.KubernetesConfig.Validate(version)
  139. if err != nil {
  140. return err
  141. }
  142. minVersion := "1.7.0"
  143. if o.KubernetesConfig.EnableAggregatedAPIs {
  144. sv, err := semver.NewVersion(version)
  145. if err != nil {
  146. return fmt.Errorf("could not validate version %s", version)
  147. }
  148. cons, err := semver.NewConstraint("<" + minVersion)
  149. if err != nil {
  150. return fmt.Errorf("could not apply semver constraint < %s against version %s", minVersion, version)
  151. }
  152. if cons.Check(sv) {
  153. return fmt.Errorf("enableAggregatedAPIs is only available in Kubernetes version %s or greater; unable to validate for Kubernetes version %s",
  154. minVersion, version)
  155. }
  156. if o.KubernetesConfig.EnableRbac != nil {
  157. if !*o.KubernetesConfig.EnableRbac {
  158. return fmt.Errorf("enableAggregatedAPIs requires the enableRbac feature as a prerequisite")
  159. }
  160. }
  161. }
  162. if helpers.IsTrueBoolPointer(o.KubernetesConfig.EnableDataEncryptionAtRest) {
  163. sv, err := semver.NewVersion(version)
  164. if err != nil {
  165. return fmt.Errorf("could not validate version %s", version)
  166. }
  167. cons, err := semver.NewConstraint("<" + minVersion)
  168. if err != nil {
  169. return fmt.Errorf("could not apply semver constraint < %s against version %s", minVersion, version)
  170. }
  171. if cons.Check(sv) {
  172. return fmt.Errorf("enableDataEncryptionAtRest is only available in Kubernetes version %s or greater; unable to validate for Kubernetes version %s",
  173. minVersion, o.OrchestratorVersion)
  174. }
  175. if o.KubernetesConfig.EtcdEncryptionKey != "" {
  176. _, err = base64.URLEncoding.DecodeString(o.KubernetesConfig.EtcdEncryptionKey)
  177. if err != nil {
  178. return fmt.Errorf("etcdEncryptionKey must be base64 encoded. Please provide a valid base64 encoded value or leave the etcdEncryptionKey empty to auto-generate the value")
  179. }
  180. }
  181. }
  182. if helpers.IsTrueBoolPointer(o.KubernetesConfig.EnableEncryptionWithExternalKms) {
  183. sv, _ := semver.NewVersion(version)
  184. minVersion := "1.10.0"
  185. cons, _ := semver.NewConstraint("<" + minVersion)
  186. if cons.Check(sv) {
  187. return fmt.Errorf("enableEncryptionWithExternalKms is only available in Kubernetes version %s or greater; unable to validate for Kubernetes version %s",
  188. minVersion, o.OrchestratorVersion)
  189. }
  190. }
  191. if helpers.IsTrueBoolPointer(o.KubernetesConfig.EnablePodSecurityPolicy) {
  192. if !helpers.IsTrueBoolPointer(o.KubernetesConfig.EnableRbac) {
  193. return fmt.Errorf("enablePodSecurityPolicy requires the enableRbac feature as a prerequisite")
  194. }
  195. sv, err := semver.NewVersion(version)
  196. if err != nil {
  197. return fmt.Errorf("could not validate version %s", version)
  198. }
  199. minVersion := "1.8.0"
  200. cons, err := semver.NewConstraint("<" + minVersion)
  201. if err != nil {
  202. return fmt.Errorf("could not apply semver constraint < %s against version %s", minVersion, version)
  203. }
  204. if cons.Check(sv) {
  205. return fmt.Errorf("enablePodSecurityPolicy is only supported in acs-engine for Kubernetes version %s or greater; unable to validate for Kubernetes version %s",
  206. minVersion, version)
  207. }
  208. }
  209. }
  210. case OpenShift:
  211. // TODO: add appropriate additional validation logic
  212. if o.OrchestratorVersion != common.OpenShiftVersionUnstable {
  213. version := common.RationalizeReleaseAndVersion(
  214. o.OrchestratorType,
  215. o.OrchestratorRelease,
  216. o.OrchestratorVersion,
  217. false)
  218. if version == "" {
  219. return fmt.Errorf("OrchestratorProfile is not able to be rationalized, check supported Release or Version")
  220. }
  221. }
  222. if o.OpenShiftConfig == nil || o.OpenShiftConfig.ClusterUsername == "" || o.OpenShiftConfig.ClusterPassword == "" {
  223. return fmt.Errorf("ClusterUsername and ClusterPassword must both be specified")
  224. }
  225. default:
  226. return fmt.Errorf("OrchestratorProfile has unknown orchestrator: %s", o.OrchestratorType)
  227. }
  228. } else {
  229. switch o.OrchestratorType {
  230. case DCOS, Kubernetes:
  231. version := common.RationalizeReleaseAndVersion(
  232. o.OrchestratorType,
  233. o.OrchestratorRelease,
  234. o.OrchestratorVersion,
  235. false)
  236. if version == "" {
  237. patchVersion := common.GetValidPatchVersion(o.OrchestratorType, o.OrchestratorVersion)
  238. // if there isn't a supported patch version for this version fail
  239. if patchVersion == "" {
  240. return fmt.Errorf("the following user supplied OrchestratorProfile configuration is not supported: OrchestratorType: %s, OrchestratorRelease: %s, OrchestratorVersion: %s. Please check supported Release or Version for this build of acs-engine", o.OrchestratorType, o.OrchestratorRelease, o.OrchestratorVersion)
  241. }
  242. }
  243. }
  244. }
  245. if (o.OrchestratorType != Kubernetes && o.OrchestratorType != OpenShift) && o.KubernetesConfig != nil {
  246. return fmt.Errorf("KubernetesConfig can be specified only when OrchestratorType is Kubernetes or OpenShift")
  247. }
  248. if o.OrchestratorType != OpenShift && o.OpenShiftConfig != nil {
  249. return fmt.Errorf("OpenShiftConfig can be specified only when OrchestratorType is OpenShift")
  250. }
  251. if o.OrchestratorType != DCOS && o.DcosConfig != nil && (*o.DcosConfig != DcosConfig{}) {
  252. return fmt.Errorf("DcosConfig can be specified only when OrchestratorType is DCOS")
  253. }
  254. return nil
  255. }
  256. func validateImageNameAndGroup(name, resourceGroup string) error {
  257. if name == "" && resourceGroup != "" {
  258. return errors.New("imageName needs to be specified when imageResourceGroup is provided")
  259. }
  260. if name != "" && resourceGroup == "" {
  261. return errors.New("imageResourceGroup needs to be specified when imageName is provided")
  262. }
  263. return nil
  264. }
  265. // Validate implements APIObject
  266. func (m *MasterProfile) Validate(o *OrchestratorProfile) error {
  267. if o.OrchestratorType == OpenShift && m.Count != 1 {
  268. return errors.New("openshift can only deployed with one master")
  269. }
  270. if m.ImageRef != nil {
  271. if err := validateImageNameAndGroup(m.ImageRef.Name, m.ImageRef.ResourceGroup); err != nil {
  272. return err
  273. }
  274. }
  275. return validateDNSName(m.DNSPrefix)
  276. }
  277. // Validate implements APIObject
  278. func (a *AgentPoolProfile) Validate(orchestratorType string) error {
  279. // Don't need to call validate.Struct(a)
  280. // It is handled by Properties.Validate()
  281. if e := validatePoolName(a.Name); e != nil {
  282. return e
  283. }
  284. // for Kubernetes, we don't support AgentPoolProfile.DNSPrefix
  285. if orchestratorType == Kubernetes {
  286. if e := validate.Var(a.DNSPrefix, "len=0"); e != nil {
  287. return fmt.Errorf("AgentPoolProfile.DNSPrefix must be empty for Kubernetes")
  288. }
  289. if e := validate.Var(a.Ports, "len=0"); e != nil {
  290. return fmt.Errorf("AgentPoolProfile.Ports must be empty for Kubernetes")
  291. }
  292. }
  293. if a.DNSPrefix != "" {
  294. if e := validateDNSName(a.DNSPrefix); e != nil {
  295. return e
  296. }
  297. if len(a.Ports) > 0 {
  298. if e := validateUniquePorts(a.Ports, a.Name); e != nil {
  299. return e
  300. }
  301. } else {
  302. a.Ports = []int{80, 443, 8080}
  303. }
  304. } else {
  305. if e := validate.Var(a.Ports, "len=0"); e != nil {
  306. return fmt.Errorf("AgentPoolProfile.Ports must be empty when AgentPoolProfile.DNSPrefix is empty for Orchestrator: %s", string(orchestratorType))
  307. }
  308. }
  309. if len(a.DiskSizesGB) > 0 {
  310. if e := validate.Var(a.StorageProfile, "eq=StorageAccount|eq=ManagedDisks"); e != nil {
  311. return fmt.Errorf("property 'StorageProfile' must be set to either '%s' or '%s' when attaching disks", StorageAccount, ManagedDisks)
  312. }
  313. if e := validate.Var(a.AvailabilityProfile, "eq=VirtualMachineScaleSets|eq=AvailabilitySet"); e != nil {
  314. return fmt.Errorf("property 'AvailabilityProfile' must be set to either '%s' or '%s' when attaching disks", VirtualMachineScaleSets, AvailabilitySet)
  315. }
  316. if a.StorageProfile == StorageAccount && (a.AvailabilityProfile == VirtualMachineScaleSets) {
  317. return fmt.Errorf("VirtualMachineScaleSets does not support storage account attached disks. Instead specify 'StorageAccount': '%s' or specify AvailabilityProfile '%s'", ManagedDisks, AvailabilitySet)
  318. }
  319. }
  320. if len(a.Ports) == 0 && len(a.DNSPrefix) > 0 {
  321. return fmt.Errorf("AgentPoolProfile.Ports must be non empty when AgentPoolProfile.DNSPrefix is specified")
  322. }
  323. if a.ImageRef != nil {
  324. return validateImageNameAndGroup(a.ImageRef.Name, a.ImageRef.ResourceGroup)
  325. }
  326. return nil
  327. }
  328. // Validate implements APIObject
  329. func (o *OrchestratorVersionProfile) Validate() error {
  330. // The only difference compared with OrchestratorProfile.Validate is
  331. // Here we use strings.EqualFold, the other just string comparison.
  332. // Rationalize orchestrator type should be done from versioned to unversioned
  333. // I will go ahead to simplify this
  334. return o.OrchestratorProfile.Validate(false)
  335. }
  336. func validateKeyVaultSecrets(secrets []KeyVaultSecrets, requireCertificateStore bool) error {
  337. for _, s := range secrets {
  338. if len(s.VaultCertificates) == 0 {
  339. return fmt.Errorf("Invalid KeyVaultSecrets must have no empty VaultCertificates")
  340. }
  341. if s.SourceVault == nil {
  342. return fmt.Errorf("missing SourceVault in KeyVaultSecrets")
  343. }
  344. if s.SourceVault.ID == "" {
  345. return fmt.Errorf("KeyVaultSecrets must have a SourceVault.ID")
  346. }
  347. for _, c := range s.VaultCertificates {
  348. if _, e := url.Parse(c.CertificateURL); e != nil {
  349. return fmt.Errorf("Certificate url was invalid. received error %s", e)
  350. }
  351. if e := validateName(c.CertificateStore, "KeyVaultCertificate.CertificateStore"); requireCertificateStore && e != nil {
  352. return fmt.Errorf("%s for certificates in a WindowsProfile", e)
  353. }
  354. }
  355. }
  356. return nil
  357. }
  358. // Validate implements APIObject
  359. func (l *LinuxProfile) Validate() error {
  360. // Don't need to call validate.Struct(l)
  361. // It is handled by Properties.Validate()
  362. if e := validate.Var(l.SSH.PublicKeys[0].KeyData, "required"); e != nil {
  363. return fmt.Errorf("KeyData in LinuxProfile.SSH.PublicKeys cannot be empty string")
  364. }
  365. if e := validateKeyVaultSecrets(l.Secrets, false); e != nil {
  366. return e
  367. }
  368. return nil
  369. }
  370. func handleValidationErrors(e validator.ValidationErrors) error {
  371. // Override any version specific validation error message
  372. // common.HandleValidationErrors if the validation error message is general
  373. return common.HandleValidationErrors(e)
  374. }
  375. // Validate implements APIObject
  376. func (w *WindowsProfile) Validate() error {
  377. if e := validate.Var(w.AdminUsername, "required"); e != nil {
  378. return fmt.Errorf("WindowsProfile.AdminUsername is required, when agent pool specifies windows")
  379. }
  380. if e := validate.Var(w.AdminPassword, "required"); e != nil {
  381. return fmt.Errorf("WindowsProfile.AdminPassword is required, when agent pool specifies windows")
  382. }
  383. if e := validateKeyVaultSecrets(w.Secrets, true); e != nil {
  384. return e
  385. }
  386. return nil
  387. }
  388. // Validate implements APIObject
  389. func (profile *AADProfile) Validate() error {
  390. if _, err := uuid.FromString(profile.ClientAppID); err != nil {
  391. return fmt.Errorf("clientAppID '%v' is invalid", profile.ClientAppID)
  392. }
  393. if _, err := uuid.FromString(profile.ServerAppID); err != nil {
  394. return fmt.Errorf("serverAppID '%v' is invalid", profile.ServerAppID)
  395. }
  396. if len(profile.TenantID) > 0 {
  397. if _, err := uuid.FromString(profile.TenantID); err != nil {
  398. return fmt.Errorf("tenantID '%v' is invalid", profile.TenantID)
  399. }
  400. }
  401. if len(profile.AdminGroupID) > 0 {
  402. if _, err := uuid.FromString(profile.AdminGroupID); err != nil {
  403. return fmt.Errorf("adminGroupID '%v' is invalid", profile.AdminGroupID)
  404. }
  405. }
  406. return nil
  407. }
  408. // Validate implements APIObject
  409. func (a *Properties) Validate(isUpdate bool) error {
  410. if e := validate.Struct(a); e != nil {
  411. return handleValidationErrors(e.(validator.ValidationErrors))
  412. }
  413. if e := a.OrchestratorProfile.Validate(isUpdate); e != nil {
  414. return e
  415. }
  416. if e := a.validateNetworkPlugin(); e != nil {
  417. return e
  418. }
  419. if e := a.validateNetworkPolicy(); e != nil {
  420. return e
  421. }
  422. if e := a.validateNetworkPluginPlusPolicy(); e != nil {
  423. return e
  424. }
  425. if e := a.validateContainerRuntime(); e != nil {
  426. return e
  427. }
  428. if e := a.validateAddons(); e != nil {
  429. return e
  430. }
  431. if e := a.validateExtensions(); e != nil {
  432. return e
  433. }
  434. if e := a.MasterProfile.Validate(a.OrchestratorProfile); e != nil {
  435. return e
  436. }
  437. if e := validateUniqueProfileNames(a.AgentPoolProfiles); e != nil {
  438. return e
  439. }
  440. if a.OrchestratorProfile.OrchestratorType == Kubernetes {
  441. useManagedIdentity := (a.OrchestratorProfile.KubernetesConfig != nil &&
  442. a.OrchestratorProfile.KubernetesConfig.UseManagedIdentity)
  443. if !useManagedIdentity {
  444. if a.ServicePrincipalProfile == nil {
  445. return fmt.Errorf("ServicePrincipalProfile must be specified with Orchestrator %s", a.OrchestratorProfile.OrchestratorType)
  446. }
  447. if e := validate.Var(a.ServicePrincipalProfile.ClientID, "required"); e != nil {
  448. return fmt.Errorf("the service principal client ID must be specified with Orchestrator %s", a.OrchestratorProfile.OrchestratorType)
  449. }
  450. if (len(a.ServicePrincipalProfile.Secret) == 0 && a.ServicePrincipalProfile.KeyvaultSecretRef == nil) ||
  451. (len(a.ServicePrincipalProfile.Secret) != 0 && a.ServicePrincipalProfile.KeyvaultSecretRef != nil) {
  452. return fmt.Errorf("either the service principal client secret or keyvault secret reference must be specified with Orchestrator %s", a.OrchestratorProfile.OrchestratorType)
  453. }
  454. if a.OrchestratorProfile.KubernetesConfig != nil && helpers.IsTrueBoolPointer(a.OrchestratorProfile.KubernetesConfig.EnableEncryptionWithExternalKms) && len(a.ServicePrincipalProfile.ObjectID) == 0 {
  455. return fmt.Errorf("the service principal object ID must be specified with Orchestrator %s when enableEncryptionWithExternalKms is true", a.OrchestratorProfile.OrchestratorType)
  456. }
  457. if a.ServicePrincipalProfile.KeyvaultSecretRef != nil {
  458. if e := validate.Var(a.ServicePrincipalProfile.KeyvaultSecretRef.VaultID, "required"); e != nil {
  459. return fmt.Errorf("the Keyvault ID must be specified for the Service Principle with Orchestrator %s", a.OrchestratorProfile.OrchestratorType)
  460. }
  461. if e := validate.Var(a.ServicePrincipalProfile.KeyvaultSecretRef.SecretName, "required"); e != nil {
  462. return fmt.Errorf("the Keyvault Secret must be specified for the Service Principle with Orchestrator %s", a.OrchestratorProfile.OrchestratorType)
  463. }
  464. if !keyvaultIDRegex.MatchString(a.ServicePrincipalProfile.KeyvaultSecretRef.VaultID) {
  465. return fmt.Errorf("service principal client keyvault secret reference is of incorrect format")
  466. }
  467. }
  468. }
  469. }
  470. if a.OrchestratorProfile.OrchestratorType == OpenShift && a.MasterProfile.StorageProfile != ManagedDisks {
  471. return errors.New("OpenShift orchestrator supports only ManagedDisks")
  472. }
  473. for i, agentPoolProfile := range a.AgentPoolProfiles {
  474. if e := agentPoolProfile.Validate(a.OrchestratorProfile.OrchestratorType); e != nil {
  475. return e
  476. }
  477. switch agentPoolProfile.AvailabilityProfile {
  478. case AvailabilitySet:
  479. case VirtualMachineScaleSets:
  480. case "":
  481. default:
  482. {
  483. return fmt.Errorf("unknown availability profile type '%s' for agent pool '%s'. Specify either %s, or %s", agentPoolProfile.AvailabilityProfile, agentPoolProfile.Name, AvailabilitySet, VirtualMachineScaleSets)
  484. }
  485. }
  486. if a.OrchestratorProfile.OrchestratorType == OpenShift && agentPoolProfile.AvailabilityProfile != AvailabilitySet {
  487. return fmt.Errorf("Only AvailabilityProfile: AvailabilitySet is supported for Orchestrator 'OpenShift'")
  488. }
  489. validRoles := []AgentPoolProfileRole{AgentPoolProfileRoleEmpty}
  490. if a.OrchestratorProfile.OrchestratorType == OpenShift {
  491. validRoles = append(validRoles, AgentPoolProfileRoleInfra)
  492. }
  493. var found bool
  494. for _, validRole := range validRoles {
  495. if agentPoolProfile.Role == validRole {
  496. found = true
  497. break
  498. }
  499. }
  500. if !found {
  501. return fmt.Errorf("Role %q is not supported for Orchestrator %s", agentPoolProfile.Role, a.OrchestratorProfile.OrchestratorType)
  502. }
  503. /* this switch statement is left to protect newly added orchestrators until they support Managed Disks*/
  504. if agentPoolProfile.StorageProfile == ManagedDisks {
  505. switch a.OrchestratorProfile.OrchestratorType {
  506. case DCOS:
  507. case Swarm:
  508. case Kubernetes:
  509. case OpenShift:
  510. case SwarmMode:
  511. default:
  512. return fmt.Errorf("HA volumes are currently unsupported for Orchestrator %s", a.OrchestratorProfile.OrchestratorType)
  513. }
  514. }
  515. if a.OrchestratorProfile.OrchestratorType == OpenShift && agentPoolProfile.StorageProfile != ManagedDisks {
  516. return errors.New("OpenShift orchestrator supports only ManagedDisks")
  517. }
  518. if len(agentPoolProfile.CustomNodeLabels) > 0 {
  519. switch a.OrchestratorProfile.OrchestratorType {
  520. case DCOS:
  521. case Kubernetes:
  522. for k, v := range agentPoolProfile.CustomNodeLabels {
  523. if e := validateKubernetesLabelKey(k); e != nil {
  524. return e
  525. }
  526. if e := validateKubernetesLabelValue(v); e != nil {
  527. return e
  528. }
  529. }
  530. default:
  531. return fmt.Errorf("Agent Type attributes are only supported for DCOS and Kubernetes")
  532. }
  533. }
  534. // validation for VMSS for Kubernetes
  535. if a.OrchestratorProfile.OrchestratorType == Kubernetes && (agentPoolProfile.AvailabilityProfile == VirtualMachineScaleSets || len(agentPoolProfile.AvailabilityProfile) == 0) {
  536. version := common.RationalizeReleaseAndVersion(
  537. a.OrchestratorProfile.OrchestratorType,
  538. a.OrchestratorProfile.OrchestratorRelease,
  539. a.OrchestratorProfile.OrchestratorVersion,
  540. false)
  541. if version == "" {
  542. return fmt.Errorf("the following user supplied OrchestratorProfile configuration is not supported: OrchestratorType: %s, OrchestratorRelease: %s, OrchestratorVersion: %s. Please check supported Release or Version for this build of acs-engine", a.OrchestratorProfile.OrchestratorType, a.OrchestratorProfile.OrchestratorRelease, a.OrchestratorProfile.OrchestratorVersion)
  543. }
  544. sv, err := semver.NewVersion(version)
  545. if err != nil {
  546. return fmt.Errorf("could not validate version %s", version)
  547. }
  548. minVersion := "1.10.0"
  549. cons, err := semver.NewConstraint("<" + minVersion)
  550. if err != nil {
  551. return fmt.Errorf("could not apply semver constraint < %s against version %s", minVersion, version)
  552. }
  553. if cons.Check(sv) {
  554. return fmt.Errorf("VirtualMachineScaleSets are only available in Kubernetes version %s or greater; unable to validate for Kubernetes version %s",
  555. minVersion, version)
  556. }
  557. }
  558. // validation for instanceMetadata using VMSS on Kubernetes
  559. if a.OrchestratorProfile.OrchestratorType == Kubernetes && (agentPoolProfile.AvailabilityProfile == VirtualMachineScaleSets || len(agentPoolProfile.AvailabilityProfile) == 0) {
  560. version := common.RationalizeReleaseAndVersion(
  561. a.OrchestratorProfile.OrchestratorType,
  562. a.OrchestratorProfile.OrchestratorRelease,
  563. a.OrchestratorProfile.OrchestratorVersion,
  564. false)
  565. if version == "" {
  566. return fmt.Errorf("the following user supplied OrchestratorProfile configuration is not supported: OrchestratorType: %s, OrchestratorRelease: %s, OrchestratorVersion: %s. Please check supported Release or Version for this build of acs-engine", a.OrchestratorProfile.OrchestratorType, a.OrchestratorProfile.OrchestratorRelease, a.OrchestratorProfile.OrchestratorVersion)
  567. }
  568. sv, err := semver.NewVersion(version)
  569. if err != nil {
  570. return fmt.Errorf("could not validate version %s", version)
  571. }
  572. minVersion := "1.10.2"
  573. cons, err := semver.NewConstraint("<" + minVersion)
  574. if err != nil {
  575. return fmt.Errorf("could not apply semver constraint < %s against version %s", minVersion, version)
  576. }
  577. if a.OrchestratorProfile.KubernetesConfig != nil && a.OrchestratorProfile.KubernetesConfig.UseInstanceMetadata != nil {
  578. if *a.OrchestratorProfile.KubernetesConfig.UseInstanceMetadata && cons.Check(sv) {
  579. return fmt.Errorf("VirtualMachineScaleSets with instance metadata is supported for Kubernetes version %s or greater. Please set \"useInstanceMetadata\": false in \"kubernetesConfig\"", minVersion)
  580. }
  581. } else {
  582. if cons.Check(sv) {
  583. return fmt.Errorf("VirtualMachineScaleSets with instance metadata is supported for Kubernetes version %s or greater. Please set \"useInstanceMetadata\": false in \"kubernetesConfig\"", minVersion)
  584. }
  585. }
  586. }
  587. if a.OrchestratorProfile.OrchestratorType == Kubernetes && (agentPoolProfile.AvailabilityProfile == VirtualMachineScaleSets || len(agentPoolProfile.AvailabilityProfile) == 0) && agentPoolProfile.StorageProfile == StorageAccount {
  588. return fmt.Errorf("VirtualMachineScaleSets does not support %s disks. Please specify \"storageProfile\": \"%s\" (recommended) or \"availabilityProfile\": \"%s\"", StorageAccount, ManagedDisks, AvailabilitySet)
  589. }
  590. if a.OrchestratorProfile.OrchestratorType == Kubernetes {
  591. if i == 0 {
  592. continue
  593. }
  594. if a.AgentPoolProfiles[i].AvailabilityProfile != a.AgentPoolProfiles[0].AvailabilityProfile {
  595. return fmt.Errorf("mixed mode availability profiles are not allowed. Please set either VirtualMachineScaleSets or AvailabilitySet in availabilityProfile for all agent pools")
  596. }
  597. }
  598. if agentPoolProfile.OSType == Windows {
  599. if e := validate.Var(a.WindowsProfile, "required"); e != nil {
  600. return fmt.Errorf("WindowsProfile must not be empty since agent pool '%s' specifies windows", agentPoolProfile.Name)
  601. }
  602. switch a.OrchestratorProfile.OrchestratorType {
  603. case DCOS:
  604. case Swarm:
  605. case SwarmMode:
  606. case Kubernetes:
  607. var version string
  608. if a.HasWindows() {
  609. version = common.RationalizeReleaseAndVersion(
  610. a.OrchestratorProfile.OrchestratorType,
  611. a.OrchestratorProfile.OrchestratorRelease,
  612. a.OrchestratorProfile.OrchestratorVersion,
  613. true)
  614. } else {
  615. version = common.RationalizeReleaseAndVersion(
  616. a.OrchestratorProfile.OrchestratorType,
  617. a.OrchestratorProfile.OrchestratorRelease,
  618. a.OrchestratorProfile.OrchestratorVersion,
  619. false)
  620. }
  621. if version == "" {
  622. return fmt.Errorf("the following user supplied OrchestratorProfile configuration is not supported: OrchestratorType: %s, OrchestratorRelease: %s, OrchestratorVersion: %s. Please check supported Release or Version for this build of acs-engine", a.OrchestratorProfile.OrchestratorType, a.OrchestratorProfile.OrchestratorRelease, a.OrchestratorProfile.OrchestratorVersion)
  623. }
  624. if supported, ok := common.AllKubernetesWindowsSupportedVersions[version]; !ok || !supported {
  625. return fmt.Errorf("Orchestrator %s version %s does not support Windows", a.OrchestratorProfile.OrchestratorType, version)
  626. }
  627. default:
  628. return fmt.Errorf("Orchestrator %s does not support Windows", a.OrchestratorProfile.OrchestratorType)
  629. }
  630. if e := a.WindowsProfile.Validate(); e != nil {
  631. return e
  632. }
  633. }
  634. }
  635. if e := a.LinuxProfile.Validate(); e != nil {
  636. return e
  637. }
  638. if e := validateVNET(a); e != nil {
  639. return e
  640. }
  641. if a.AADProfile != nil {
  642. if a.OrchestratorProfile.OrchestratorType != Kubernetes {
  643. return fmt.Errorf("'aadProfile' is only supported by orchestrator '%v'", Kubernetes)
  644. }
  645. if e := a.AADProfile.Validate(); e != nil {
  646. return e
  647. }
  648. }
  649. switch a.OrchestratorProfile.OrchestratorType {
  650. case OpenShift:
  651. if a.AzProfile == nil || a.AzProfile.Location == "" ||
  652. a.AzProfile.ResourceGroup == "" || a.AzProfile.SubscriptionID == "" ||
  653. a.AzProfile.TenantID == "" {
  654. return fmt.Errorf("'azProfile' must be supplied in full for orchestrator '%v'", OpenShift)
  655. }
  656. default:
  657. if a.AzProfile != nil {
  658. return fmt.Errorf("'azProfile' is only supported by orchestrator '%v'", OpenShift)
  659. }
  660. }
  661. for _, extension := range a.ExtensionProfiles {
  662. if extension.ExtensionParametersKeyVaultRef != nil {
  663. if e := validate.Var(extension.ExtensionParametersKeyVaultRef.VaultID, "required"); e != nil {
  664. return fmt.Errorf("the Keyvault ID must be specified for Extension %s", extension.Name)
  665. }
  666. if e := validate.Var(extension.ExtensionParametersKeyVaultRef.SecretName, "required"); e != nil {
  667. return fmt.Errorf("the Keyvault Secret must be specified for Extension %s", extension.Name)
  668. }
  669. if !keyvaultIDRegex.MatchString(extension.ExtensionParametersKeyVaultRef.VaultID) {
  670. return fmt.Errorf("Extension %s's keyvault secret reference is of incorrect format", extension.Name)
  671. }
  672. }
  673. }
  674. if a.WindowsProfile != nil && a.WindowsProfile.WindowsImageSourceURL != "" {
  675. if a.OrchestratorProfile.OrchestratorType != DCOS && a.OrchestratorProfile.OrchestratorType != Kubernetes {
  676. return fmt.Errorf("Windows Custom Images are only supported if the Orchestrator Type is DCOS or Kubernetes")
  677. }
  678. }
  679. return nil
  680. }
  681. // Validate validates the KubernetesConfig.
  682. func (a *KubernetesConfig) Validate(k8sVersion string) error {
  683. // number of minimum retries allowed for kubelet to post node status
  684. const minKubeletRetries = 4
  685. // k8s versions that have cloudprovider backoff enabled
  686. var backoffEnabledVersions = common.AllKubernetesSupportedVersions
  687. // at present all supported versions allow for cloudprovider backoff
  688. // disallow backoff for future versions thusly:
  689. // for version := range []string{"1.11.0", "1.11.1", "1.11.2"} {
  690. // backoffEnabledVersions[version] = false
  691. // }
  692. // k8s versions that have cloudprovider rate limiting enabled (currently identical with backoff enabled versions)
  693. ratelimitEnabledVersions := backoffEnabledVersions
  694. if a.ClusterSubnet != "" {
  695. _, subnet, err := net.ParseCIDR(a.ClusterSubnet)
  696. if err != nil {
  697. return fmt.Errorf("OrchestratorProfile.KubernetesConfig.ClusterSubnet '%s' is an invalid subnet", a.ClusterSubnet)
  698. }
  699. if a.NetworkPlugin == "azure" {
  700. ones, bits := subnet.Mask.Size()
  701. if bits-ones <= 8 {
  702. return fmt.Errorf("OrchestratorProfile.KubernetesConfig.ClusterSubnet '%s' must reserve at least 9 bits for nodes", a.ClusterSubnet)
  703. }
  704. }
  705. }
  706. if a.DockerBridgeSubnet != "" {
  707. _, _, err := net.ParseCIDR(a.DockerBridgeSubnet)
  708. if err != nil {
  709. return fmt.Errorf("OrchestratorProfile.KubernetesConfig.DockerBridgeSubnet '%s' is an invalid subnet", a.DockerBridgeSubnet)
  710. }
  711. }
  712. if a.MaxPods != 0 {
  713. if a.MaxPods < KubernetesMinMaxPods {
  714. return fmt.Errorf("OrchestratorProfile.KubernetesConfig.MaxPods '%v' must be at least %v", a.MaxPods, KubernetesMinMaxPods)
  715. }
  716. }
  717. if a.KubeletConfig != nil {
  718. if _, ok := a.KubeletConfig["--node-status-update-frequency"]; ok {
  719. val := a.KubeletConfig["--node-status-update-frequency"]
  720. _, err := time.ParseDuration(val)
  721. if err != nil {
  722. return fmt.Errorf("--node-status-update-frequency '%s' is not a valid duration", val)
  723. }
  724. }
  725. }
  726. if _, ok := a.ControllerManagerConfig["--node-monitor-grace-period"]; ok {
  727. _, err := time.ParseDuration(a.ControllerManagerConfig["--node-monitor-grace-period"])
  728. if err != nil {
  729. return fmt.Errorf("--node-monitor-grace-period '%s' is not a valid duration", a.ControllerManagerConfig["--node-monitor-grace-period"])
  730. }
  731. }
  732. if a.KubeletConfig != nil {
  733. if _, ok := a.KubeletConfig["--node-status-update-frequency"]; ok {
  734. if _, ok := a.ControllerManagerConfig["--node-monitor-grace-period"]; ok {
  735. nodeStatusUpdateFrequency, _ := time.ParseDuration(a.KubeletConfig["--node-status-update-frequency"])
  736. ctrlMgrNodeMonitorGracePeriod, _ := time.ParseDuration(a.ControllerManagerConfig["--node-monitor-grace-period"])
  737. kubeletRetries := ctrlMgrNodeMonitorGracePeriod.Seconds() / nodeStatusUpdateFrequency.Seconds()
  738. if kubeletRetries < minKubeletRetries {
  739. return fmt.Errorf("acs-engine requires that --node-monitor-grace-period(%f)s be larger than nodeStatusUpdateFrequency(%f)s by at least a factor of %d; ", ctrlMgrNodeMonitorGracePeriod.Seconds(), nodeStatusUpdateFrequency.Seconds(), minKubeletRetries)
  740. }
  741. }
  742. }
  743. if _, ok := a.KubeletConfig["--non-masquerade-cidr"]; ok {
  744. if _, _, err := net.ParseCIDR(a.KubeletConfig["--non-masquerade-cidr"]); err != nil {
  745. return fmt.Errorf("--non-masquerade-cidr kubelet config '%s' is an invalid CIDR string", a.KubeletConfig["--non-masquerade-cidr"])
  746. }
  747. }
  748. }
  749. if _, ok := a.ControllerManagerConfig["--pod-eviction-timeout"]; ok {
  750. _, err := time.ParseDuration(a.ControllerManagerConfig["--pod-eviction-timeout"])
  751. if err != nil {
  752. return fmt.Errorf("--pod-eviction-timeout '%s' is not a valid duration", a.ControllerManagerConfig["--pod-eviction-timeout"])
  753. }
  754. }
  755. if _, ok := a.ControllerManagerConfig["--route-reconciliation-period"]; ok {
  756. _, err := time.ParseDuration(a.ControllerManagerConfig["--route-reconciliation-period"])
  757. if err != nil {
  758. return fmt.Errorf("--route-reconciliation-period '%s' is not a valid duration", a.ControllerManagerConfig["--route-reconciliation-period"])
  759. }
  760. }
  761. if a.CloudProviderBackoff {
  762. if !backoffEnabledVersions[k8sVersion] {
  763. return fmt.Errorf("cloudprovider backoff functionality not available in kubernetes version %s", k8sVersion)
  764. }
  765. }
  766. if a.CloudProviderRateLimit {
  767. if !ratelimitEnabledVersions[k8sVersion] {
  768. return fmt.Errorf("cloudprovider rate limiting functionality not available in kubernetes version %s", k8sVersion)
  769. }
  770. }
  771. if a.DNSServiceIP != "" || a.ServiceCidr != "" {
  772. if a.DNSServiceIP == "" {
  773. return errors.New("OrchestratorProfile.KubernetesConfig.ServiceCidr must be specified when DNSServiceIP is")
  774. }
  775. if a.ServiceCidr == "" {
  776. return errors.New("OrchestratorProfile.KubernetesConfig.DNSServiceIP must be specified when ServiceCidr is")
  777. }
  778. dnsIP := net.ParseIP(a.DNSServiceIP)
  779. if dnsIP == nil {
  780. return fmt.Errorf("OrchestratorProfile.KubernetesConfig.DNSServiceIP '%s' is an invalid IP address", a.DNSServiceIP)
  781. }
  782. _, serviceCidr, err := net.ParseCIDR(a.ServiceCidr)
  783. if err != nil {
  784. return fmt.Errorf("OrchestratorProfile.KubernetesConfig.ServiceCidr '%s' is an invalid CIDR subnet", a.ServiceCidr)
  785. }
  786. // Finally validate that the DNS ip is within the subnet
  787. if !serviceCidr.Contains(dnsIP) {
  788. return fmt.Errorf("OrchestratorProfile.KubernetesConfig.DNSServiceIP '%s' is not within the ServiceCidr '%s'", a.DNSServiceIP, a.ServiceCidr)
  789. }
  790. // and that the DNS IP is _not_ the subnet broadcast address
  791. broadcast := common.IP4BroadcastAddress(serviceCidr)
  792. if dnsIP.Equal(broadcast) {
  793. return fmt.Errorf("OrchestratorProfile.KubernetesConfig.DNSServiceIP '%s' cannot be the broadcast address of ServiceCidr '%s'", a.DNSServiceIP, a.ServiceCidr)
  794. }
  795. // and that the DNS IP is _not_ the first IP in the service subnet
  796. firstServiceIP := common.CidrFirstIP(serviceCidr.IP)
  797. if firstServiceIP.Equal(dnsIP) {
  798. return fmt.Errorf("OrchestratorProfile.KubernetesConfig.DNSServiceIP '%s' cannot be the first IP of ServiceCidr '%s'", a.DNSServiceIP, a.ServiceCidr)
  799. }
  800. }
  801. // Validate that we have a valid etcd version
  802. if e := isValidEtcdVersion(a.EtcdVersion); e != nil {
  803. return e
  804. }
  805. if a.UseCloudControllerManager != nil && *a.UseCloudControllerManager || a.CustomCcmImage != "" {
  806. sv, _ := semver.NewVersion(k8sVersion)
  807. cons, _ := semver.NewConstraint("<" + "1.8.0")
  808. if cons.Check(sv) {
  809. return fmt.Errorf("OrchestratorProfile.KubernetesConfig.UseCloudControllerManager and OrchestratorProfile.KubernetesConfig.CustomCcmImage not available in kubernetes version %s", k8sVersion)
  810. }
  811. }
  812. return nil
  813. }
  814. func (a *Properties) validateNetworkPlugin() error {
  815. var networkPlugin string
  816. switch a.OrchestratorProfile.OrchestratorType {
  817. case Kubernetes:
  818. if a.OrchestratorProfile.KubernetesConfig != nil {
  819. networkPlugin = a.OrchestratorProfile.KubernetesConfig.NetworkPlugin
  820. }
  821. default:
  822. return nil
  823. }
  824. // Check NetworkPlugin has a valid value.
  825. valid := false
  826. for _, plugin := range NetworkPluginValues {
  827. if networkPlugin == plugin {
  828. valid = true
  829. break
  830. }
  831. }
  832. if !valid {
  833. return fmt.Errorf("unknown networkPlugin '%s' specified", networkPlugin)
  834. }
  835. return nil
  836. }
  837. func (a *Properties) validateNetworkPolicy() error {
  838. var networkPolicy string
  839. switch a.OrchestratorProfile.OrchestratorType {
  840. case Kubernetes:
  841. if a.OrchestratorProfile.KubernetesConfig != nil {
  842. networkPolicy = a.OrchestratorProfile.KubernetesConfig.NetworkPolicy
  843. }
  844. default:
  845. return nil
  846. }
  847. // Check NetworkPolicy has a valid value.
  848. valid := false
  849. for _, plugin := range NetworkPolicyValues {
  850. if networkPolicy == plugin {
  851. valid = true
  852. break
  853. }
  854. }
  855. if !valid {
  856. return fmt.Errorf("unknown networkPolicy '%s' specified", networkPolicy)
  857. }
  858. // Temporary safety check, to be removed when Windows support is added.
  859. if (networkPolicy == "calico" || networkPolicy == "cilium" || networkPolicy == "flannel") && a.HasWindows() {
  860. return fmt.Errorf("networkPolicy '%s' is not supporting windows agents", networkPolicy)
  861. }
  862. return nil
  863. }
  864. func (a *Properties) validateNetworkPluginPlusPolicy() error {
  865. var config k8sNetworkConfig
  866. if a.OrchestratorProfile.KubernetesConfig != nil {
  867. config.networkPlugin = a.OrchestratorProfile.KubernetesConfig.NetworkPlugin
  868. }
  869. if a.OrchestratorProfile.KubernetesConfig != nil {
  870. config.networkPolicy = a.OrchestratorProfile.KubernetesConfig.NetworkPolicy
  871. }
  872. for _, c := range networkPluginPlusPolicyAllowed {
  873. if c.networkPlugin == config.networkPlugin && c.networkPolicy == config.networkPolicy {
  874. return nil
  875. }
  876. }
  877. return fmt.Errorf("networkPolicy '%s' is not supported with networkPlugin '%s'", config.networkPolicy, config.networkPlugin)
  878. }
  879. func (a *Properties) validateContainerRuntime() error {
  880. var containerRuntime string
  881. switch a.OrchestratorProfile.OrchestratorType {
  882. case Kubernetes:
  883. if a.OrchestratorProfile.KubernetesConfig != nil {
  884. containerRuntime = a.OrchestratorProfile.KubernetesConfig.ContainerRuntime
  885. }
  886. default:
  887. return nil
  888. }
  889. // Check ContainerRuntime has a valid value.
  890. valid := false
  891. for _, runtime := range ContainerRuntimeValues {
  892. if containerRuntime == runtime {
  893. valid = true
  894. break
  895. }
  896. }
  897. if !valid {
  898. return fmt.Errorf("unknown containerRuntime %q specified", containerRuntime)
  899. }
  900. // Make sure we don't use clear containers on windows.
  901. if (containerRuntime == "clear-containers" || containerRuntime == "containerd") && a.HasWindows() {
  902. return fmt.Errorf("containerRuntime %q is not supporting windows agents", containerRuntime)
  903. }
  904. return nil
  905. }
  906. func (a *Properties) validateAddons() error {
  907. if a.OrchestratorProfile.KubernetesConfig != nil && a.OrchestratorProfile.KubernetesConfig.Addons != nil {
  908. var isAvailabilitySets bool
  909. for _, agentPool := range a.AgentPoolProfiles {
  910. if agentPool.IsAvailabilitySets() {
  911. isAvailabilitySets = true
  912. }
  913. }
  914. for _, addon := range a.OrchestratorProfile.KubernetesConfig.Addons {
  915. if addon.Name == "cluster-autoscaler" && *addon.Enabled && isAvailabilitySets {
  916. return fmt.Errorf("Cluster Autoscaler add-on can only be used with VirtualMachineScaleSets. Please specify \"availabilityProfile\": \"%s\"", VirtualMachineScaleSets)
  917. }
  918. }
  919. }
  920. return nil
  921. }
  922. func (a *Properties) validateExtensions() error {
  923. for _, agentPool := range a.AgentPoolProfiles {
  924. if len(agentPool.Extensions) != 0 && (len(agentPool.AvailabilityProfile) == 0 || agentPool.IsVirtualMachineScaleSets()) {
  925. return fmt.Errorf("Extensions are currently not supported with VirtualMachineScaleSets. Please specify \"availabilityProfile\": \"%s\"", AvailabilitySet)
  926. }
  927. }
  928. return nil
  929. }
  930. func validateName(name string, label string) error {
  931. if name == "" {
  932. return fmt.Errorf("%s must be a non-empty value", label)
  933. }
  934. return nil
  935. }
  936. func validatePoolName(poolName string) error {
  937. // we will cap at length of 12 and all lowercase letters since this makes up the VMName
  938. poolNameRegex := `^([a-z][a-z0-9]{0,11})$`
  939. re, err := regexp.Compile(poolNameRegex)
  940. if err != nil {
  941. return err
  942. }
  943. submatches := re.FindStringSubmatch(poolName)
  944. if len(submatches) != 2 {
  945. 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)
  946. }
  947. return nil
  948. }
  949. func validateDNSName(dnsName string) error {
  950. dnsNameRegex := `^([A-Za-z][A-Za-z0-9-]{1,43}[A-Za-z0-9])$`
  951. re, err := regexp.Compile(dnsNameRegex)
  952. if err != nil {
  953. return err
  954. }
  955. if !re.MatchString(dnsName) {
  956. 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))
  957. }
  958. return nil
  959. }
  960. func validateUniqueProfileNames(profiles []*AgentPoolProfile) error {
  961. profileNames := make(map[string]bool)
  962. for _, profile := range profiles {
  963. if _, ok := profileNames[profile.Name]; ok {
  964. return fmt.Errorf("profile name '%s' already exists, profile names must be unique across pools", profile.Name)
  965. }
  966. profileNames[profile.Name] = true
  967. }
  968. return nil
  969. }
  970. func validateUniquePorts(ports []int, name string) error {
  971. portMap := make(map[int]bool)
  972. for _, port := range ports {
  973. if _, ok := portMap[port]; ok {
  974. return fmt.Errorf("agent profile '%s' has duplicate port '%d', ports must be unique", name, port)
  975. }
  976. portMap[port] = true
  977. }
  978. return nil
  979. }
  980. func validateKubernetesLabelValue(v string) error {
  981. if !(len(v) == 0) && !labelValueRegex.MatchString(v) {
  982. return fmt.Errorf("Label value '%s' is invalid. Valid label values must be 63 characters or less and must be empty or begin and end with an alphanumeric character ([a-z0-9A-Z]) with dashes (-), underscores (_), dots (.), and alphanumerics between", v)
  983. }
  984. return nil
  985. }
  986. func validateKubernetesLabelKey(k string) error {
  987. if !labelKeyRegex.MatchString(k) {
  988. return fmt.Errorf("Label key '%s' is invalid. Valid label keys have two segments: an optional prefix and name, separated by a slash (/). The name segment is required and must be 63 characters or less, beginning and ending with an alphanumeric character ([a-z0-9A-Z]) with dashes (-), underscores (_), dots (.), and alphanumerics between. The prefix is optional. If specified, the prefix must be a DNS subdomain: a series of DNS labels separated by dots (.), not longer than 253 characters in total, followed by a slash (/)", k)
  989. }
  990. prefix := strings.Split(k, "/")
  991. if len(prefix) != 1 && len(prefix[0]) > labelKeyPrefixMaxLength {
  992. return fmt.Errorf("Label key prefix '%s' is invalid. If specified, the prefix must be no longer than 253 characters in total", k)
  993. }
  994. return nil
  995. }
  996. func validateVNET(a *Properties) error {
  997. isCustomVNET := a.MasterProfile.IsCustomVNET()
  998. for _, agentPool := range a.AgentPoolProfiles {
  999. if agentPool.IsCustomVNET() != isCustomVNET {
  1000. 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")
  1001. }
  1002. }
  1003. if isCustomVNET {
  1004. subscription, resourcegroup, vnetname, _, e := GetVNETSubnetIDComponents(a.MasterProfile.VnetSubnetID)
  1005. if e != nil {
  1006. return e
  1007. }
  1008. for _, agentPool := range a.AgentPoolProfiles {
  1009. agentSubID, agentRG, agentVNET, _, err := GetVNETSubnetIDComponents(agentPool.VnetSubnetID)
  1010. if err != nil {
  1011. return err
  1012. }
  1013. if agentSubID != subscription ||
  1014. agentRG != resourcegroup ||
  1015. agentVNET != vnetname {
  1016. 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)")
  1017. }
  1018. }
  1019. masterFirstIP := net.ParseIP(a.MasterProfile.FirstConsecutiveStaticIP)
  1020. if masterFirstIP == nil {
  1021. return fmt.Errorf("MasterProfile.FirstConsecutiveStaticIP (with VNET Subnet specification) '%s' is an invalid IP address", a.MasterProfile.FirstConsecutiveStaticIP)
  1022. }
  1023. if a.MasterProfile.VnetCidr != "" {
  1024. _, _, err := net.ParseCIDR(a.MasterProfile.VnetCidr)
  1025. if err != nil {
  1026. return fmt.Errorf("MasterProfile.VnetCidr '%s' contains invalid cidr notation", a.MasterProfile.VnetCidr)
  1027. }
  1028. }
  1029. }
  1030. return nil
  1031. }
  1032. // GetVNETSubnetIDComponents extract subscription, resourcegroup, vnetname, subnetname from the vnetSubnetID
  1033. func GetVNETSubnetIDComponents(vnetSubnetID string) (string, string, string, string, error) {
  1034. vnetSubnetIDRegex := `^\/subscriptions\/([^\/]*)\/resourceGroups\/([^\/]*)\/providers\/Microsoft.Network\/virtualNetworks\/([^\/]*)\/subnets\/([^\/]*)$`
  1035. re, err := regexp.Compile(vnetSubnetIDRegex)
  1036. if err != nil {
  1037. return "", "", "", "", err
  1038. }
  1039. submatches := re.FindStringSubmatch(vnetSubnetID)
  1040. if len(submatches) != 4 {
  1041. return "", "", "", "", err
  1042. }
  1043. return submatches[1], submatches[2], submatches[3], submatches[4], nil
  1044. }