Manage Kubernetes namespaces
In the world of DevOps, efficiency and automation are key. In many cases, you want to be able to chain actions - where one action will only be triggered once its preceding action is completed, sometimes involving manual approval as part of the workflow.
This guides aims to:
- Demonstrate the power and flexibility of chaining self-service actions and automations in Port.
- Provide a real-world example of how to use action chaining to manage Kubernetes namespaces using Port.
Scenario overview
This guide will demonstrate how to create the following workflow:
-
A user requests to delete a Kubernetes namespace via a self-service action in Port.
-
The action creates a
workflow_delete_namespace
entity in Port representing the deletion request. -
An automation is triggered when the
workflow_delete_namespace
entity is created, checking the namespace's details and updating the entity's status in Port. -
Another automation is triggered when the
workflow_delete_namespace
entity's status is updated to "Namespace found, waiting for approval", sending a Slack message requesting approval from an admin. -
The deletion request is approved by an admin using another self-service action, which finally deletes the namespace.
Prerequisites
- Port account: If you don't have a Port account, you will need to create one.
- This guide includes the creation of actions and automations that use a GitLab pipeline as their backend. While the logic of the backend can be implemented using other Git providers or CI/CD tools, the examples in this guide are specific to GitLab.
Data Model
This guide uses a blueprint-driven approach to manage the deletion of Kubernetes namespaces, which includes two blueprints that you will need to create in your portal.
To create a blueprint, go to the data model page, click on the + Blueprint
button, then click on the Edit JSON
button to define the blueprint using JSON instead of the UI.
Create two blueprints using the following JSON definitions:
k8s namespace
{
"identifier": "k8s_namespace",
"description": "This blueprint represents a k8s Namespace",
"title": "K8S Namespace",
"icon": "Cluster",
"schema": {
"properties": {
"creationTimestamp": {
"type": "string",
"title": "Created",
"format": "date-time",
"description": "When the Namespace was created"
},
"labels": {
"type": "object",
"title": "Labels",
"description": "Labels of the Namespace"
},
"_data_source": {
"type": "string",
"title": "Origin data source",
"description": "The ingestion source of the data (used for debug)"
}
},
"required": []
},
"mirrorProperties": {},
"calculationProperties": {},
"aggregationProperties": {},
"relations": {}
}
workflow_delete_namespace
Note that this blueprint has a relation to the k8s_namespace blueprint.
{
"identifier": "workflow_delete_namespace",
"description": "Represent all delete namespaces workflows",
"title": "Workflow Delete Namespace",
"icon": "Cluster",
"schema": {
"properties": {
"approved_by": {
"icon": "LeftArrow",
"type": "string",
"title": "Approved by",
"format": "user"
},
"current_status": {
"icon": "DefaultProperty",
"title": "Current status",
"type": "string",
"default": "Checking namespace details",
"enum": [
"Checking namespace details",
"Namespace found, waiting for approval",
"Approved/Deleted",
"Namespace cannot be deleted "
],
"enumColors": {
"Checking namespace details": "orange",
"Namespace found, waiting for approval": "turquoise",
"Approved/Deleted": "green",
"Namespace cannot be deleted ": "red"
}
}
},
"required": []
},
"mirrorProperties": {},
"calculationProperties": {},
"aggregationProperties": {},
"relations": {
"namespace": {
"title": "Namespace",
"target": "k8s_namespace",
"required": false,
"many": false
}
}
}
Actions & automations
This workflow uses two self-service actions and two automations to manage the deletion of Kubernetes namespaces.
Just like blueprints, actions and automations can also be defined using JSON.
Self-service actions
To create a self-service action, go to the self-service page of your portal, click on the + Action
button, then click on the Edit JSON
button to define the action using JSON instead of the UI.
Create the following actions using the JSON definitions below:
Request deletion of a namespace
This action can be executed by a developer to request the deletion of a Kubernetes namespace. It creates a workflow_delete_namespace
entity in Port representing the deletion request.
-
Action definition:
Request deletion of a namespace
{
"identifier": "request_for_deleting_namespace",
"title": "Request deletion of a namespace",
"icon": "Infinity",
"description": "Request the deletetion of a k8s namespace",
"trigger": {
"type": "self-service",
"operation": "DAY-2",
"userInputs": {
"properties": {},
"required": [],
"order": []
},
"blueprintIdentifier": "k8s_namespace"
},
"invocationMethod": {
"type": "UPSERT_ENTITY",
"blueprintIdentifier": "workflow_delete_namespace",
"mapping": {
"identifier": "{{ .entity.identifier + \"_deletion_request_workflow_\" + .trigger.at}}",
"title": "{{ .entity.identifier + \"_deletion_request_workflow\"}}",
"icon": "Cluster",
"properties": {},
"relations": {
"namespace": "{{ .entity.identifier}}"
}
}
},
"requiredApproval": false,
"approvalNotification": {
"type": "email"
}
} -
Action backend:
This action uses theUPSERT_ENTITY
backend type, which directly creates/updates a new entity in Port. No additional backend logic is required.
Approve deletion of a namespace
This action can be executed by an admin to approve the deletion of a Kubernetes namespace. It deletes the namespace and updates the status of the workflow_delete_namespace
entity in Port to "Approved/Deleted".
-
Action definition:
Remember to replace theGITLAB_PROJECT_ID
andGITLAB_TRIGGER_TOKEN
placeholders with your values.
To learn how to obtain these values, see the GitLab backend documentation.Approve deletion of a namespace
{
"identifier": "delete_namespace",
"title": "Approve the deletion of a k8s namespace",
"trigger": {
"type": "self-service",
"operation": "DAY-2",
"userInputs": {
"properties": {},
"required": [],
"order": []
},
"condition": {
"type": "SEARCH",
"rules": [
{
"operator": "=",
"property": "current_status",
"value": "Namespace found, waiting for approval"
}
],
"combinator": "and"
},
"blueprintIdentifier": "workflow_delete_namespace"
},
"invocationMethod": {
"type": "WEBHOOK",
"url": "https://gitlab.com/api/v4/projects/{GITLAB_PROJECT_ID}/ref/main/trigger/pipeline?token={GITLAB_TRIGGER_TOKEN}",
"agent": false,
"synchronized": false,
"method": "POST",
"headers": {
"RUN_ID": "{{ .run.id }}"
},
"body": {
"runId": "{{ .run.id }}",
"blueprint": "{{ .action.blueprint }}",
"entity": "{{ .entity }}",
"namespace": "{{ .entity.relations.namespace }}",
"workflow": "{{ .entity.identifier }}",
"approved_by": "{{.trigger.by.user.email}}"
}
},
"requiredApproval": false
} -
Action backend:
This action uses theWEBHOOK
backend type, which triggers a webhook to a specified URL. In this case, the webhook triggers a GitLab pipeline to check the namespace:GitLab pipeline
stages:
- prerequisites
- delete-namespace
- port-update
image:
name: hashicorp/terraform:light
entrypoint:
- '/usr/bin/env'
- 'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
variables:
PORT_CLIENT_ID: ${PORT_CLIENT_ID}
PORT_CLIENT_SECRET: ${PORT_CLIENT_SECRET}
PORT_API_URL: "https://api.getport.io/v1/blueprints/k8s_namespace/entities"
PORT_API_WORKFLOW_URL: "https://api.getport.io/v1/blueprints/workflow_delete_namespace/entities"
PORT_ACTIONS_URL: "https://api.getport.io/v1/actions/runs"
before_script:
- apk update
- apk add --upgrade curl jq -q
fetch-port-access-token:
stage: prerequisites
except:
- pushes
script:
- |
echo "Getting access token from Port API"
accessToken=$(curl -X POST \
-H 'Content-Type: application/json' \
-d '{"clientId": "'"$PORT_CLIENT_ID"'", "clientSecret": "'"$PORT_CLIENT_SECRET"'"}' \
-s 'https://api.getport.io/v1/auth/access_token' | jq -r '.accessToken')
echo "ACCESS_TOKEN=$accessToken" >> data.env
runId=$(cat $TRIGGER_PAYLOAD | jq -r '.runId')
namespace=$(cat $TRIGGER_PAYLOAD | jq -r '.namespace')
workflow=$(cat $TRIGGER_PAYLOAD | jq -r '.workflow')
approved_by=$(cat $TRIGGER_PAYLOAD | jq -r '.approved_by')
echo "runId=$runId" >> data.env
echo "namespace=$namespace" >> data.env
echo "workflow=$workflow" >> data.env
echo "approved_by=$approved_by" >> data.env
curl -X POST \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $accessToken" \
-d '{"message":"🏃♂️ Deleting namespace"}' \
"https://api.getport.io/v1/actions/runs/$runId/logs"
curl -X PATCH \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $accessToken" \
-d '{"link":"'"$CI_PIPELINE_URL"'"}' \
"https://api.getport.io/v1/actions/runs/$runId"
artifacts:
reports:
dotenv: data.env
delete-namespace:
stage: delete-namespace
dependencies:
- fetch-port-access-token
script:
- |
curl -X 'DELETE' \
-H 'accept: application/json' \
-H "Authorization: Bearer $ACCESS_TOKEN" \
"${PORT_API_URL}/${namespace}?delete_dependents=false"
curl -X PATCH \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-d "{\"identifier\": \"${workflow}\", \"properties\": {\"current_status\": \"Approved/Deleted\"},{\"approved_by\": {\"${approved_by}\""}"}}" \
"${PORT_API_WORKFLOW_URL}/${workflow}"
# For demonstration purposes, simulate success status
curl -X PATCH \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-d "{\"identifier\": \"${workflow}\", \"properties\": {\"current_status\": \"Approved/Deleted\", \"approved_by\": \"${approved_by}\"}}" \
"${PORT_API_WORKFLOW_URL}/${workflow}"
send-data-to-port:
stage: port-update
dependencies:
- fetch-port-access-token
script:
- |
# For demonstration purposes, simulate success status
curl -X PATCH \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-d '{"status": "SUCCESS", "message": {"run_status": "Run completed successfully!"}}' \
"${PORT_ACTIONS_URL}/$runId"
Automations
To create an automation, go to the automations page of your portal, then click on the + Automation
button.
Create the following automations using the JSON definitions below:
Check namespace details
This automation is triggered when an entity of type workflow_delete_namespace
is created. It checks the details of the namespace and updates the entity's status in Port.
-
Automation definition:
Remember to replace theGITLAB_PROJECT_ID
andGITLAB_TRIGGER_TOKEN
placeholders with your values.
To learn how to obtain these values, see the GitLab backend documentation.Check namespace details
{
"identifier": "triggerNamspaceCheckerAfterRequest",
"title": "Check namespace details",
"description": "When a request is made to delete a k8s namespace, check its details.",
"trigger": {
"type": "automation",
"event": {
"type": "ENTITY_CREATED",
"blueprintIdentifier": "workflow_delete_namespace"
}
},
"invocationMethod": {
"type": "WEBHOOK",
"url": "https://gitlab.com/api/v4/projects/{GITLAB_PROJECT_ID}/ref/main/trigger/pipeline?token={GITLAB_TRIGGER_TOKEN}",
"agent": false,
"synchronized": false,
"method": "POST",
"headers": {
"RUN_ID": "{{ .run.id }}"
},
"body": {
"RUN_ID": "{{ .run.id }}",
"workflow": "{{ .event.context.entityIdentifier }}"
}
},
"publish": true
} -
Automation backend:
This automation triggers a webhook to a specified URL. In this case, the webhook triggers a GitLab pipeline to check the namespace:GitLab pipeline
stages:
- prerequisites
- check-namespace
- port-update
image:
name: hashicorp/terraform:light
entrypoint:
- '/usr/bin/env'
- 'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
variables:
PORT_CLIENT_ID: ${PORT_CLIENT_ID}
PORT_CLIENT_SECRET: ${PORT_CLIENT_SECRET}
PORT_API_URL: "https://api.getport.io/v1/blueprints/workflow_delete_namespace/entities"
PORT_ACTIONS_URL: "https://api.getport.io/v1/actions/runs"
PORT_API_URL_NAMESPACE: "https://api.getport.io/v1/blueprints/k8s_namespace/entities/"
before_script:
- apk update
- apk add --upgrade curl jq -q
fetch-port-access-token:
stage: prerequisites
except:
- pushes
script:
- |
echo "Getting access token from Port API"
accessToken=$(curl -X POST \
-H 'Content-Type: application/json' \
-d '{"clientId": "'"$PORT_CLIENT_ID"'", "clientSecret": "'"$PORT_CLIENT_SECRET"'"}' \
-s 'https://api.getport.io/v1/auth/access_token' | jq -r '.accessToken')
echo "ACCESS_TOKEN=$accessToken" >> data.env
cat $TRIGGER_PAYLOAD
runId=$(cat $TRIGGER_PAYLOAD | jq -r '.RUN_ID')
workflow=$(cat $TRIGGER_PAYLOAD | jq -r '.workflow')
echo "RUN_ID=$runId" >> data.env
echo "workflow=$workflow" >> data.env
curl -X POST \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $accessToken" \
-d '{"message":"🏃♂️ Checking namespace data"}' \
"https://api.getport.io/v1/actions/runs/$runId/logs"
curl -X PATCH \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $accessToken" \
-d '{"link":"'"$CI_PIPELINE_URL"'"}' \
"https://api.getport.io/v1/actions/runs/$runId"
artifacts:
reports:
dotenv: data.env
check-namespace:
stage: check-namespace
needs:
- job: fetch-port-access-token
artifacts: true
script:
- echo "Checking Namespace"
- sleep 1
send-data-to-port:
stage: port-update
dependencies:
- fetch-port-access-token
script:
- |
curl -X PATCH \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-d "{\"identifier\": \"${workflow}\", \"properties\": {\"current_status\": \"Namespace found, waiting for approval\"}}" \
"${PORT_API_URL}/${workflow}"
# For demonstration purposes, simulate success status
curl -X PATCH \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-d '{"status": "SUCCESS", "message": {"run_status": "Run completed successfully!"}}' \
"${PORT_ACTIONS_URL}/$RUN_ID"
Request approval
This automation is triggered when the status of an entity of type workflow_delete_namespace
is updated to "Namespace found, waiting for approval". It sends a Slack message requesting approval from an admin.
-
Automation definition:
Request approval via Slack notification
{
"identifier": "triggerSlackNotificationAfterChecker",
"title": "Request approval via Slack notification",
"trigger": {
"type": "automation",
"event": {
"type": "ENTITY_UPDATED",
"blueprintIdentifier": "workflow_delete_namespace"
},
"condition": {
"type": "JQ",
"expressions": [
".diff.before.properties.current_status == \"Checking namespace details\"",
".diff.after.properties.current_status == \"Namespace found, waiting for approval\""
],
"combinator": "and"
}
},
"invocationMethod": {
"type": "WEBHOOK",
"url": "https://hooks.slack.com/services/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",
"agent": false,
"synchronized": true,
"method": "POST",
"headers": {
"RUN_ID": "{{ .run.id }}"
},
"body": {
"text": "The namespace {{.event.diff.before.relations.namespace}} had been requested for deletion, here is the url for the entity https://app.getport.io/workflow_delete_namespaceEntity?identifier={{.event.context.entityIdentifier}}"
}
},
"publish": true
} -
Automation backend:
This automation triggers a webhook to a specified URL. In this case, the webhook sends a Slack message to a specified channel. The message body is defined in theinvocationMethod.body
field of the automation definition.
Conclusion
Once all of the above components are created, you will have a the necessary setup to run the workflow described in the scenario overview.
You can use this chaining mechanism to create complex workflows for many use-cases, that involve multiple actions and automations, enabling you to streamline your DevOps processes.