Skip to content

Commit

Permalink
feat: Implement NS cleanup (#26)
Browse files Browse the repository at this point in the history
feat: Implement NS cleanup
  • Loading branch information
fmarek-kindred authored Apr 29, 2024
1 parent ad8e33c commit f4c02b6
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 0 deletions.
1 change: 1 addition & 0 deletions brownie/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
coverage
brownie-ns-list.tmp.json
35 changes: 35 additions & 0 deletions brownie/scripts/clean-ns.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/bin/bash
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# This script is invoked from the periodic CI pipeline. It will fetch the list of children
# namespaces from the kubernetes and pass that list into the node app. The node app will determine
# whether the namespace is old and should be removed or it hasn't been aged yet and should be
# kept in the cluster. If the "--dry-run" parameter is "false" then old namespaces will be removed.
# The actual removal of namespace is delegeated to
# ../k8s-deployer/scripts/k8s-manage-namespace.sh
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
K8S_NAMESPACE=$1
BROWNIE_TIMEOUT=$2
DRY_RUN=$3

if [ "${DRY_RUN}" == "" ]; then DRY_RUN="true"; fi

usage="Example: $0 my-parent-namespace 3days"
if [ "${K8S_NAMESPACE}" == "" ];
then
echo "Missing 1st parameter namespace"
echo $usage
exit 1
fi

if [ "${BROWNIE_TIMEOUT}" == "" ];
then
echo "Missing 2nd parameter namespace retention period"
echo $usage
exit 1
fi

LOG_FILE="./brownie-ns-list.tmp.json"

kubectl get ns -l "${K8S_NAMESPACE}.tree.hnc.x-k8s.io/depth=1" -ojson | jq '.items' > $LOG_FILE

node dist/src/k8ns/index.js --dry-run $DRY_RUN --ns-file $LOG_FILE --retention-period $BROWNIE_TIMEOUT
42 changes: 42 additions & 0 deletions brownie/src/k8ns/core.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import * as fs from "fs"

import { Config as BrownieConfig } from "../config.js"
import { logger } from "../logger.js"

export const HNC_PARENT_ANNOTATION = "hnc.x-k8s.io/subnamespace-of"
export interface ChildNamespace {
metadata: {
creationTimestamp: string,
name: string,
annotations: Array<string>
}
}

export class Config {
constructor(
readonly dryRun: boolean,
readonly nsList: Array<ChildNamespace>,
readonly retentionMinutes: number,
) {}
}

export const loadConfig = (params: Map<string, string>): Config => {
const dryRunRaw = params.get(BrownieConfig.PARAM_DRY_RUN)
const nsDataFile = params.get("--ns-file")
const retentionRaw = params.get(BrownieConfig.PARAM_RETENTION_PERIOD)

const nsListRaw = fs.readFileSync(`${ nsDataFile }`).toString("utf-8")
let nsList: Array<ChildNamespace> = null
try {
nsList = JSON.parse(nsListRaw)
} catch (e) {
logger.warn("loadConfig(): Could not parse the list of namespaces:\n%s", nsListRaw)
throw new Error(`Unable to parse raw list of namespaces in '${ nsDataFile }'`, { cause: e })
}

return new Config(
"false" !== dryRunRaw.toLowerCase(),
nsList,
BrownieConfig.parseRetention(retentionRaw)
)
}
72 changes: 72 additions & 0 deletions brownie/src/k8ns/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import * as Shell from "node:child_process"

import { logger } from "../logger.js"
import * as Core from "./core.js"

const main = async () => {
const params = new Map<string, string>()

let deletedCount = 0
for (let i = 2; i < process.argv.length; i+=2) {
params.set(process.argv[i], process.argv[i + 1])
}
const config = Core.loadConfig(params)
if (config.nsList.length == 0) {
logger.debug("There are no namespaces to delete")
return
}

logger.info("Analysing the list of %s namespaces", config.nsList.length)

for (let ns of config.nsList) {
logger.info("")

if (!ns.metadata.creationTimestamp) {
logger.info("Namespace '%s' does not have 'creationTimestamp' information. Skipping...", ns.metadata.name)
continue
}

const nsName = ns.metadata.name
const ageInMinutes = (new Date().getTime() - Date.parse(ns.metadata.creationTimestamp)) / 60_000
logger.info("Namespace '%s' was created at: %s. It is %s minutes old", nsName, ns.metadata.creationTimestamp, ageInMinutes.toFixed(2))
if (ageInMinutes < config.retentionMinutes) {
logger.info("Namespace '%s' hasn't aged enough. Will be cleaned after %s minutes", nsName, (config.retentionMinutes - ageInMinutes).toFixed(2))
continue
}

const parentNsName = ns.metadata.annotations[Core.HNC_PARENT_ANNOTATION]
if (!parentNsName) {
logger.warn("The child namespace '%s', does not have '%s' annotation. It might be misconfigured, hence need to be dealt with manually. Skipping...", nsName, Core.HNC_PARENT_ANNOTATION)
continue
}

logger.info("Deleting namespace '%s' under '%s'", nsName, parentNsName)
if (config.dryRun) {

logger.info("Namespace '%s > %s' has NOT been deleted (dry run mode).", parentNsName, nsName)
deletedCount++

} else {

try {

const script = "../k8s-deployer/scripts/k8s-manage-namespace.sh"
const scriptParams = [ parentNsName, "delete", nsName, 120 ]
Shell.spawnSync(script, scriptParams, { stdio: [ 'inherit', 'inherit', 'inherit' ] })
deletedCount++

} catch (e) {
logger.error("Unable to delete namespace %s > %s. Error: %s", parentNsName, nsName, e.message)
if (e.cause) logger.error(e.cause)
if (e.stack) logger.error("Stack:\n%s", e.stack)
}
}
}
}

main()
.catch(e => {
logger.error("Message: %s", e.message)
if (e.cause) logger.error(e.cause)
if (e.stack) logger.error("Stack:\n%s", e.stack)
})

0 comments on commit f4c02b6

Please sign in to comment.