@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 +6 -0
- package/kubernetes.js +58 -44
- package/lib/aws-efs.js +34 -4
- package/package.json +2 -1
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
|
|
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
|
-
|
|
425
|
-
|
|
426
|
-
|
|
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
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
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
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
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
|
-
|
|
442
|
-
|
|
443
|
-
|
|
448
|
+
if (drvOptions?.storage?.size) {
|
|
449
|
+
pvc.spec.resources.requests.storage = drvOptions.storage.size
|
|
450
|
+
}
|
|
444
451
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
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
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
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.
|
|
739
|
-
if (times > 0 && err.
|
|
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
|
|
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:
|
|
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
|
|
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.
|
|
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"
|