1
- const { KubeConfig, AppsV1Api } = require ( '@kubernetes/client-node' ) ;
2
-
3
1
const { parseTimeDurationString, msToHumanReadable } = require ( './time' ) ;
4
2
5
- const kc = new KubeConfig ( ) ;
6
- kc . loadFromCluster ( ) ;
7
-
8
- const k8sAppsApi = kc . makeApiClient ( AppsV1Api ) ;
3
+ const {
4
+ getTeamInstances,
5
+ getTeamJuiceShopInstances,
6
+ getNamespaces,
7
+ deleteNamespaceForTeam,
8
+ } = require ( './kubernetes' ) ;
9
9
10
10
const MaxInactiveDuration = process . env [ 'MAX_INACTIVE_DURATION' ] ;
11
+ const ShouldDelete = process . env [ 'SHOULD_DELETE' ] ;
11
12
const MaxInactiveDurationInMs = parseTimeDurationString ( MaxInactiveDuration ) ;
12
13
13
14
if ( MaxInactiveDurationInMs === null ) {
@@ -19,85 +20,148 @@ if (MaxInactiveDurationInMs === null) {
19
20
"30m" for 30 minutes.
20
21
` ) ;
21
22
}
22
-
23
+ /**
24
+ * The approach is simple:
25
+ * 1. Get all namespaces and loop over them
26
+ * 2. For each namespace, get all wrongsecrets deployments and loop over them
27
+ * 3. For each deployment, check if it has been inactive for more than the configured duration
28
+ * 4. If it has, count it
29
+ * 5. If it hasn't, skip it
30
+ * 6. Repeat for all deployments
31
+ * 7. If the number of inactive deployments equals the number of all deployments, delete the namespace
32
+ * 8. Repeat for all namespaces
33
+ * 9. Print out some stats
34
+ * 10. Exit
35
+ */
23
36
async function main ( ) {
24
- const counts = {
25
- successful : {
26
- deployments : 0 ,
27
- services : 0 ,
28
- } ,
29
- failed : {
30
- deployments : 0 ,
31
- services : 0 ,
32
- } ,
33
- } ;
34
-
35
- console . log (
36
- `Looking for Instances & namespaces which have been inactive for more than ${ MaxInactiveDuration } .`
37
- ) ;
38
- const instances = await k8sAppsApi . listDeploymentForAllNamespaces (
39
- true ,
40
- undefined ,
41
- undefined ,
42
- 'app in (wrongsecrets, virtualdesktop)' ,
43
- 200
44
- ) ;
45
-
46
- console . log ( `Found ${ instances . body . items . length } instances. Checking their activity.` ) ;
37
+ console . log ( 'Starting WrongSecrets Instance Cleanup' ) ;
38
+ console . log ( '' ) ;
39
+ console . log ( 'Configuration:' ) ;
40
+ console . log ( ` MAX_INACTIVE_DURATION: ${ MaxInactiveDuration } ` ) ;
41
+ console . log ( ` SHOULD_DELETE: ${ ShouldDelete } ` ) ;
42
+ console . log ( '' ) ;
43
+ const namespacesNames = await listOldNamespaces ( ) ;
44
+ const counts = await deleteNamespaces ( namespacesNames ) ;
45
+ console . log ( '' ) ;
46
+ console . log ( 'Finished WrongSecrets Instance Cleanup' ) ;
47
+ console . log ( '' ) ;
48
+ console . log ( 'Successful deletions:' ) ;
49
+ console . log ( ` Namespaces: ${ counts . successful . namespaces } ` ) ;
50
+ console . log ( 'Failed deletions:' ) ;
51
+ console . log ( ` Namespaces: ${ counts . failed . namespaces } ` ) ;
52
+ }
47
53
48
- for ( const instance of instances . body . items ) {
49
- const instanceName = instance . metadata . name ;
50
- const lastConnectTimestamps = parseInt (
51
- instance . metadata . annotations [ 'wrongsecrets-ctf-party/lastRequest' ] ,
52
- 10
53
- ) ;
54
+ async function listOldNamespaces ( ) {
55
+ var namespacesNames = [ ] ;
56
+ // Get all namespaces
57
+ const namespaces = await getNamespaces ( ) ;
58
+ console . log ( `Found ${ namespaces . body . items . length } namespaces. Checking their activity.` ) ;
59
+ // Loop over all namespaces
60
+ for ( const namespace of namespaces . body . items ) {
61
+ console . log ( `Checking ${ namespace . metadata . name } namespaces activity.` ) ;
62
+ // Get the name of the namespace
63
+ const namespaceName = namespace . metadata . name ;
64
+ console . log ( 'Looking for deployments in namespace ' + namespaceName ) ;
65
+ // Get all deployments in the namespace
66
+ const deployments = await getTeamJuiceShopInstances ( namespaceName ) ;
67
+ // Check if deployments exist, if not, skip the namespace
68
+ // IMPORTANT: In case the namespace is completely empty, it will not be deleted as the user might want it as a playground
69
+ if ( deployments === undefined || deployments . body . items . length === 0 ) {
70
+ console . log ( `No wrongsecrets deployments found in namespace ${ namespaceName } . Skipping...` ) ;
71
+ continue ;
72
+ }
73
+ // Check if the namespace is only used by the wrongsecrets instance. If not, skip the namespace
74
+ const AllDeployments = await getTeamInstances ( namespaceName ) ;
75
+ if ( AllDeployments . body . items . length > deployments . body . items . length ) {
76
+ console . log ( `Namespace ${ namespaceName } is used by other deployments. Skipping...` ) ;
77
+ continue ;
78
+ }
79
+ console . log ( `Found ${ deployments . body . items . length } wrongsecrets deployments
80
+ in namespace ${ namespaceName } .` ) ;
81
+ //Assume all deployments are active at first
82
+ var numberOfActiveDeployments = deployments . body . items . length ;
83
+ // Loop over all deployments
84
+ for ( const deployment of deployments . body . items ) {
85
+ // Get the name of the deployment
86
+ const deploymentName = deployment . metadata . name ;
87
+ const lastConnectTimestamps = parseInt (
88
+ deployment . metadata . annotations [ 'wrongsecrets-ctf-party/lastRequest' ] ,
89
+ 10
90
+ ) ;
54
91
55
- console . log ( `Checking instance : '${ instanceName } '.` ) ;
92
+ console . log ( `Checking deployment : '${ deploymentName } '.` ) ;
56
93
57
- const currentTime = new Date ( ) . getTime ( ) ;
94
+ const currentTime = new Date ( ) . getTime ( ) ;
58
95
59
- const timeDifference = currentTime - lastConnectTimestamps ;
60
- var teamname = instance . metadata . labels . team ;
61
- if ( timeDifference > MaxInactiveDurationInMs ) {
62
- console . log (
63
- `Instance: '${ instanceName } '. Instance hasn't been used in ${ msToHumanReadable (
64
- timeDifference
65
- ) } .`
66
- ) ;
67
- console . log ( `Instance belongs to namespace ${ teamname } ` ) ;
68
- try {
69
- console . log ( `not yet implemented, but would be deleting namespace ${ teamname } now` ) ;
70
- // await k8sAppsApi.deleteNamespacedDeployment(instanceName, teamname);
71
- counts . successful . deployments ++ ;
72
- } catch ( error ) {
73
- counts . failed . deployments ++ ;
74
- console . error ( `Failed to delete namespace '${ teamname } '` ) ;
75
- console . error ( error ) ;
96
+ const timeDifference = currentTime - lastConnectTimestamps ;
97
+ var teamname = deployment . metadata . labels . team ;
98
+ if ( timeDifference > MaxInactiveDurationInMs ) {
99
+ console . log (
100
+ `Instance: '${ deploymentName } '. Instance hasn't been used in ${ msToHumanReadable (
101
+ timeDifference
102
+ ) } .`
103
+ ) ;
104
+ console . log ( `Considered inactive.` ) ;
105
+ numberOfActiveDeployments -- ;
106
+ } else {
107
+ console . log (
108
+ `Instance: '${ deploymentName } ' from '${ teamname } '. Been last active ${ msToHumanReadable (
109
+ timeDifference
110
+ ) } ago.`
111
+ ) ;
112
+ console . log ( `Considered active. The namespace will not be deleted.` ) ;
113
+ // If the deployment is active, we can break the loop
114
+ break ;
76
115
}
77
- } else {
78
- console . log (
79
- `Not deleting Instance: '${ instanceName } ' from '${ teamname } '. Been last active ${ msToHumanReadable (
80
- timeDifference
81
- ) } ago.`
82
- ) ;
116
+ }
117
+ // If all deployments are inactive, add the namespace to the list
118
+ if ( numberOfActiveDeployments === 0 ) {
119
+ console . log ( `All deployments in namespace ${ namespaceName } are inactive. Should be deleted.` ) ;
120
+ namespacesNames . push ( namespaceName ) ;
83
121
}
84
122
}
123
+ return namespacesNames ;
124
+ }
85
125
126
+ async function deleteNamespaces ( namespaceNames ) {
127
+ const counts = {
128
+ successful : {
129
+ namespaces : 0 ,
130
+ } ,
131
+ failed : {
132
+ namespaces : 0 ,
133
+ } ,
134
+ } ;
135
+ // Check if the list is empty
136
+ if ( namespaceNames === undefined || namespaceNames . length === 0 ) {
137
+ console . log ( 'No namespaces to delete.' ) ;
138
+ return counts ;
139
+ }
140
+ // Check for the SHOULD_DELETE environment variable
141
+ if ( ShouldDelete === 'false' ) {
142
+ console . log ( 'SHOULD_DELETE is set to false. Skipping deletion.' ) ;
143
+ return counts ;
144
+ }
145
+ // Loop over all namespaces
146
+ for ( const namespaceName of namespaceNames ) {
147
+ console . log ( `Deleting namespace ${ namespaceName } ...` ) ;
148
+ try {
149
+ await deleteNamespaceForTeam ( namespaceName ) ;
150
+ counts . successful . namespaces ++ ;
151
+ } catch ( err ) {
152
+ counts . failed . namespaces ++ ;
153
+ console . error ( `Failed to delete namespace ${ namespaceName } .` ) ;
154
+ // console.error(err);
155
+ }
156
+ }
86
157
return counts ;
87
158
}
159
+ module . exports = {
160
+ listOldNamespaces,
161
+ deleteNamespaces,
162
+ } ;
88
163
89
- main ( )
90
- . then ( ( counts ) => {
91
- console . log ( 'Finished WrongSecrets Instance Cleanup' ) ;
92
- console . log ( '' ) ;
93
- console . log ( 'Successful deletions:' ) ;
94
- console . log ( ` Deployments: ${ counts . successful . deployments } ` ) ;
95
- console . log ( ` Services: ${ counts . successful . services } ` ) ;
96
- console . log ( 'Failed deletions:' ) ;
97
- console . log ( ` Deployments: ${ counts . failed . deployments } ` ) ;
98
- console . log ( ` Services: ${ counts . failed . services } ` ) ;
99
- } )
100
- . catch ( ( err ) => {
101
- console . error ( 'Failed deletion tasks' ) ;
102
- console . error ( err ) ;
103
- } ) ;
164
+ main ( ) . catch ( ( err ) => {
165
+ console . error ( 'Failed deletion tasks' ) ;
166
+ console . error ( err ) ;
167
+ } ) ;
0 commit comments