@flowfuse/driver-kubernetes 2.28.2-83ca90c-202603261056.0 → 2.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ #### 2.29.0: Release
2
+
3
+ - Checking correct error field for retry (#335) @hardillb
4
+ - Bump picomatch from 4.0.3 to 4.0.4 (#338) @app/dependabot
5
+ - Bump fast-xml-parser and @aws-sdk/xml-builder (#331) @app/dependabot
6
+
1
7
  #### 2.28.1: Release
2
8
 
3
9
  - Bump actions/create-github-app-token from 2.2.1 to 3.0.0 (#321)
package/kubernetes.js CHANGED
@@ -419,43 +419,54 @@ const createCustomIngress = async (project, hostname, options) => {
419
419
 
420
420
  const createPersistentVolumeClaim = async (project, options) => {
421
421
  const namespace = this._app.config.driver.options?.projectNamespace || 'flowforge'
422
- const pvc = JSON.parse(JSON.stringify(persistentVolumeClaimTemplate))
422
+ const name = `${project.id}-pvc`
423
+ try {
424
+ await this._k8sApi.readNamespacedPersistentVolumeClaim({ name, namespace })
425
+ // exists no need to recreate
426
+ return undefined
427
+ } catch (err) {
428
+ if (err.code === 404 || err.response?.statusCode === 404) {
429
+ const pvc = JSON.parse(JSON.stringify(persistentVolumeClaimTemplate))
423
430
 
424
- const drvOptions = this._app.config.driver.options
425
- const allowedAccessModes = new Set(['ReadWriteOnce', 'ReadWriteMany', 'ReadWriteOncePod'])
426
- const configuredAccessMode = drvOptions?.storage?.accessMode
431
+ const drvOptions = this._app.config.driver.options
432
+ const allowedAccessModes = new Set(['ReadWriteOnce', 'ReadWriteMany', 'ReadWriteOncePod'])
433
+ const configuredAccessMode = drvOptions?.storage?.accessMode
427
434
 
428
- if (configuredAccessMode !== undefined) {
429
- if (!allowedAccessModes.has(configuredAccessMode)) {
430
- throw new Error(`Unsupported storage.accessMode '${configuredAccessMode}'. Allowed values: ${Array.from(allowedAccessModes).join(', ')}`)
431
- }
432
- pvc.spec.accessModes = [configuredAccessMode]
433
- }
435
+ if (configuredAccessMode !== undefined) {
436
+ if (!allowedAccessModes.has(configuredAccessMode)) {
437
+ throw new Error(`Unsupported storage.accessMode '${configuredAccessMode}'. Allowed values: ${Array.from(allowedAccessModes).join(', ')}`)
438
+ }
439
+ pvc.spec.accessModes = [configuredAccessMode]
440
+ }
434
441
 
435
- if (drvOptions?.storage?.storageClass) {
436
- pvc.spec.storageClassName = drvOptions.storage.storageClass
437
- } else if (drvOptions?.storage?.storageClassEFSTag) {
438
- pvc.spec.storageClassName = await awsEFS.lookupStorageClass(drvOptions?.storage?.storageClassEFSTag)
439
- }
442
+ if (drvOptions?.storage?.storageClass) {
443
+ pvc.spec.storageClassName = drvOptions.storage.storageClass
444
+ } else if (drvOptions?.storage?.storageClassEFSTag) {
445
+ pvc.spec.storageClassName = await awsEFS.lookupStorageClass(drvOptions?.storage?.storageClassEFSTag)
446
+ }
440
447
 
441
- if (drvOptions?.storage?.size) {
442
- pvc.spec.resources.requests.storage = drvOptions.storage.size
443
- }
448
+ if (drvOptions?.storage?.size) {
449
+ pvc.spec.resources.requests.storage = drvOptions.storage.size
450
+ }
444
451
 
445
- pvc.metadata.namespace = namespace
446
- pvc.metadata.name = `${project.id}-pvc`
447
- pvc.metadata.labels = {
448
- 'ff-project-id': project.id,
449
- 'ff-project-name': project.safeName
450
- }
451
- if (this._app.config.driver.options?.projectLabels) {
452
- pvc.metadata.labels = {
453
- ...pvc.metadata.labels,
454
- ...this._app.config.driver.options.projectLabels
452
+ pvc.metadata.namespace = namespace
453
+ pvc.metadata.name = name
454
+ pvc.metadata.labels = {
455
+ 'ff-project-id': project.id,
456
+ 'ff-project-name': project.safeName
457
+ }
458
+ if (this._app.config.driver.options?.projectLabels) {
459
+ pvc.metadata.labels = {
460
+ ...pvc.metadata.labels,
461
+ ...this._app.config.driver.options.projectLabels
462
+ }
463
+ }
464
+ console.error(`PVC: ${JSON.stringify(pvc, null, 2)}`)
465
+ return pvc
466
+ } else {
467
+ throw err
455
468
  }
456
469
  }
457
- console.error(`PVC: ${JSON.stringify(pvc, null, 2)}`)
458
- return pvc
459
470
  }
460
471
 
461
472
  const createProject = async (project, options) => {
@@ -468,17 +479,19 @@ const createProject = async (project, options) => {
468
479
  if (this._app.config.driver.options?.storage?.enabled) {
469
480
  const localPVC = await createPersistentVolumeClaim(project, options)
470
481
  // console.log(JSON.stringify(localPVC, null, 2))
471
- try {
472
- await this._k8sApi.createNamespacedPersistentVolumeClaim({ namespace, body: localPVC })
473
- } catch (err) {
474
- console.error(JSON.stringify(err))
475
- if (err.code === 409) {
476
- this._app.log.warn(`[k8s] PVC for instance ${project.id} already exists, proceeding...`)
477
- } else {
478
- if (project.state !== 'suspended') {
479
- this._app.log.error(`[k8s] Instance ${project.id} - error creating PVC: ${err.toString()} ${err.code} ${err.stack}`)
480
- // console.log(err)
481
- throw err
482
+ if (localPVC !== undefined) {
483
+ try {
484
+ await this._k8sApi.createNamespacedPersistentVolumeClaim({ namespace, body: localPVC })
485
+ } catch (err) {
486
+ console.error(JSON.stringify(err))
487
+ if (err.code === 409) {
488
+ this._app.log.warn(`[k8s] PVC for instance ${project.id} already exists, proceeding...`)
489
+ } else {
490
+ if (project.state !== 'suspended') {
491
+ this._app.log.error(`[k8s] Instance ${project.id} - error creating PVC: ${err.toString()} ${err.code} ${err.stack}`)
492
+ // console.log(err)
493
+ throw err
494
+ }
482
495
  }
483
496
  }
484
497
  }
@@ -735,8 +748,8 @@ const waitForInstanceRunning = async (endpoint) => {
735
748
  // functions to wrap k8s api functions in retry logic
736
749
  const retry = (driver, api, func, args, delay, times) => {
737
750
  return func.apply(api, args).catch(err => {
738
- driver._app.log.error(`[k8s] API call to ${func.name} failed. attempt=${driver._k8sRetries - times + 1}/${driver._k8sRetries + 1} statusCode=${err.response?.statusCode || 'N/A'} ${err.toString()}`)
739
- if (times > 0 && err.response && err.response.statusCode === 429) {
751
+ driver._app.log.error(`[k8s] API call to ${func.name} failed. attempt=${driver._k8sRetries - times + 1}/${driver._k8sRetries + 1} statusCode=${err.code || 'N/A'} ${err.toString()}`)
752
+ if (times > 0 && err.code === 429) {
740
753
  return new Promise(resolve => {
741
754
  setTimeout(() => { resolve(retry(driver, api, func, args, delay * 2, times - 1)) }, delay)
742
755
  })
@@ -807,7 +820,8 @@ module.exports = {
807
820
  this._k8sApi.deleteNamespacedPod,
808
821
  this._k8sApi.deleteNamespacedSecret,
809
822
  this._k8sApi.deleteNamespacedService,
810
- this._k8sApi.deleteNamespacedPersistentVolumeClaim
823
+ this._k8sApi.deleteNamespacedPersistentVolumeClaim,
824
+ this._k8sApi.readNamespacedPersistentVolumeClaim
811
825
  ], this)
812
826
  wrapClient(this._k8sAppApi, [
813
827
  this._k8sAppApi.createNamespacedDeployment,
package/lib/aws-efs.js CHANGED
@@ -1,4 +1,5 @@
1
- const { EFSClient, DescribeFileSystemsCommand, DescribeAccessPointsCommand } = require('@aws-sdk/client-efs')
1
+ const { EFSClient, DescribeFileSystemsCommand, DescribeAccessPointsCommand, ThrottlingException } = require('@aws-sdk/client-efs')
2
+ const retry = require('async-retry')
2
3
 
3
4
  let client
4
5
 
@@ -10,7 +11,22 @@ async function lookupStorageClass (tagName) {
10
11
  }
11
12
 
12
13
  const fsCommand = new DescribeFileSystemsCommand()
13
- const fsList = await client.send(fsCommand)
14
+ const fsList = await retry(async (bail) => {
15
+ try {
16
+ const list = await client.send(fsCommand)
17
+ return list
18
+ } catch (err) {
19
+ if (err instanceof ThrottlingException) {
20
+ throw err // retry after delay
21
+ } else {
22
+ return bail(err) // not Throttling, time to fail
23
+ }
24
+ }
25
+ },
26
+ {
27
+ retries: 5,
28
+ minTimeout: 500
29
+ })
14
30
  // console.log(JSON.stringify(fsList, null, 2))
15
31
 
16
32
  const fileSystems = []
@@ -31,11 +47,25 @@ async function lookupStorageClass (tagName) {
31
47
  // console.log(storageClass)
32
48
  const apParams = {
33
49
  FileSystemId: fsList.FileSystems[i].FileSystemId,
34
- MaxResults: 999
50
+ MaxResults: 9999 // max access points per filesystem is now 10,000
35
51
  }
36
52
  // console.log(apParams)
37
53
  const apListCommand = new DescribeAccessPointsCommand(apParams)
38
- const apList = await client.send(apListCommand)
54
+ const apList = await retry(async (bail) => {
55
+ try {
56
+ const list = await client.send(apListCommand)
57
+ return list
58
+ } catch (err) {
59
+ if (err instanceof ThrottlingException) {
60
+ throw err // retry after delay
61
+ } else {
62
+ return bail(err) // not Throttling, time to fail
63
+ }
64
+ }
65
+ }, {
66
+ retries: 5,
67
+ minTimeout: 500
68
+ })
39
69
  // fileSystems[fsList.FileSystems[i].FileSystemId]
40
70
  fileSystems.push({
41
71
  apCount: apList.AccessPoints.length,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowfuse/driver-kubernetes",
3
- "version": "2.28.2-83ca90c-202603261056.0",
3
+ "version": "2.29.0",
4
4
  "description": "Kubernetes driver for FlowFuse",
5
5
  "main": "kubernetes.js",
6
6
  "scripts": {
@@ -23,6 +23,7 @@
23
23
  "dependencies": {
24
24
  "@aws-sdk/client-efs": "^3.600.0",
25
25
  "@kubernetes/client-node": "^1.3.0",
26
+ "async-retry": "^1.3.3",
26
27
  "form-data": "^4.0.0",
27
28
  "got": "^11.8.0",
28
29
  "lodash": "^4.17.21"