Skip to main content

Lock and Unlock Service

In this guide, we will create a self-service action in Port that executes a GitHub workflow to lock and unlock a service. The locking and unlocking mechanism involves updating the locked property in Port on a production or development environment. Additionally, the action will send a Slack notification to a designated channel.

Use cases

  1. Maintenance & Critical Events: Lock deployment during maintenance, peak traffic, or critical events to maintain stability.
  2. Emergency Situations: Lock deployment in emergencies such as security breaches or vulnerabilities to mitigate risks and prevent further issues.
  3. Post-Incident Fixes: Unlock deployment to allow teams to implement necessary fixes or updates swiftly and restore system functionality.

Prerequisites

  1. Install Port's GitHub app by clicking here.
  2. Configure a Slack app that can post a message to a Slack channel. The app should have a chat:write bot scope under OAuth & Permissions.
  3. A GitHub repository in which you can trigger a workflow that we will use in this guide.

The service blueprint that was created for you as part of the onboarding process will need to be extended with additional properties. Below you can find the JSON definition for the blueprint with the required properties.
You can add these properties manually in the Port UI or use the JSON below to replace the existing blueprint.

Service blueprint (click to expand)
{
"identifier": "service",
"title": "Service",
"icon": "Github",
"schema": {
"properties": {
"readme": {
"title": "README",
"type": "string",
"format": "markdown",
"icon": "Book"
},
"url": {
"title": "URL",
"format": "url",
"type": "string",
"icon": "Link"
},
"language": {
"icon": "Git",
"type": "string",
"title": "Language",
"enum": [
"GO",
"Python",
"Node",
"React"
],
"enumColors": {
"GO": "red",
"Python": "green",
"Node": "blue",
"React": "yellow"
}
},
"slack": {
"icon": "Slack",
"type": "string",
"title": "Slack",
"format": "url"
},
"code_owners": {
"title": "Code owners",
"description": "This service's code owners",
"type": "string",
"icon": "TwoUsers"
},
"type": {
"title": "Type",
"description": "This service's type",
"type": "string",
"enum": [
"Backend",
"Frontend",
"Library"
],
"enumColors": {
"Backend": "purple",
"Frontend": "pink",
"Library": "green"
},
"icon": "DefaultProperty"
},
"lifecycle": {
"title": "Lifecycle",
"type": "string",
"enum": [
"Production",
"Staging",
"Development"
],
"enumColors": {
"Production": "green",
"Staging": "yellow",
"Development": "blue"
},
"icon": "DefaultProperty"
},
"locked_in_prod": {
"icon": "DefaultProperty",
"title": "Locked in Prod",
"type": "boolean",
"default": false
},
"locked_reason_prod": {
"icon": "DefaultProperty",
"title": "Locked Reason Prod",
"type": "string"
},
"locked_in_test": {
"icon": "DefaultProperty",
"title": "Locked in Test",
"type": "boolean",
"default": false
},
"locked_reason_test": {
"icon": "DefaultProperty",
"title": "Locked Reason Test",
"type": "string"
}
},
"required": []
},
"mirrorProperties": {},
"calculationProperties": {},
"aggregationProperties": {},
"relations": {}
}
Lock property

Our Service blueprint has a boolean property called locked_in_prod. When the action is triggered, we will update the value of this field to true if locking and false if unlocking. The locked_reason_prod property will be updated to reflect the reason for the lock or unlock. The same logic applies to the development environment (locked_in_test).

Create Github workflow

Follow these steps to get started:

  1. Create the following GitHub Action secrets:
    • PORT_CLIENT_ID - Port Client ID learn more
    • PORT_CLIENT_SECRET - Port Client Secret learn more
    • SLACK_BOT_TOKEN - The app's Bot Token from the OAuth & Permissions page on Slack
    • SLACK_CHANNEL_ID - The Slack channel id

  1. Create two Port actions in the self-service page on the Service blueprint with the following JSON definitions:
Port Action: Lock Service (click to expand)
tip
  • <GITHUB-ORG> - your GitHub organization or user name.
  • <GITHUB-REPO-NAME> - your GitHub repository name.
{
"identifier": "lock_service",
"title": "Lock Service",
"icon": "Lock",
"description": "Lock service in Port",
"trigger": {
"type": "self-service",
"operation": "DAY-2",
"userInputs": {
"properties": {
"reason": {
"type": "string",
"title": "Reason"
},
"environment": {
"type": "string",
"title": "Environment",
"enum": [
"Production",
"Staging",
"Development"
],
"enumColors": {
"Production": "green",
"Staging": "orange",
"Development": "blue"
}
}
},
"required": [],
"order": [
"reason"
]
},
"blueprintIdentifier": "service"
},
"invocationMethod": {
"type": "GITHUB",
"org": "<GITHUB-ORG>",
"repo": "<GITHUB-REPO-NAME>",
"workflow": "lock-service.yml",
"workflowInputs": {
"reason": "{{ .inputs.\"reason\" }}",
"environment": "{{ .inputs.\"environment\" }}",
"port_context": {
"blueprint": "{{.action.blueprint}}",
"entity": "{{.entity}}",
"runId": "{{.run.id}}",
"trigger": "{{ .trigger }}"
}
},
"reportWorkflowStatus": true
},
"requiredApproval": false
}
Port Action: Unlock Service (click to expand)
tip
  • <GITHUB-ORG> - your GitHub organization or user name.
  • <GITHUB-REPO-NAME> - your GitHub repository name.
{
"identifier": "unlock_service",
"title": "Unlock Service",
"icon": "Unlock",
"description": "Unlock service in Port",
"trigger": {
"type": "self-service",
"operation": "DAY-2",
"userInputs": {
"properties": {
"reason": {
"type": "string",
"title": "Reason"
},
"environment": {
"type": "string",
"title": "Environment",
"enum": [
"Production",
"Staging",
"Development"
],
"enumColors": {
"Production": "green",
"Staging": "orange",
"Development": "blue"
}
}
},
"required": [],
"order": [
"reason"
]
},
"blueprintIdentifier": "service"
},
"invocationMethod": {
"type": "GITHUB",
"org": "<GITHUB-ORG>",
"repo": "<GITHUB-REPO-NAME>",
"workflow": "unlock-service.yml",
"workflowInputs": {
"reason": "{{ .inputs.\"reason\" }}",
"environment": "{{ .inputs.\"environment\" }}",
"port_context": {
"blueprint": "{{.action.blueprint}}",
"entity": "{{.entity}}",
"runId": "{{.run.id}}",
"trigger": "{{ .trigger }}"
}
},
"reportWorkflowStatus": true
},
"requiredApproval": false
}

  1. In your Github repository, create a workflow file under .github/workflows/lock-service.yml with the following content:
GitHub workflow script to lock a service (click to expand)
lock-service.yml
name: Lock Service in Port
on:
workflow_dispatch:
inputs:
environment:
type: string
required: true
reason:
type: string
required: true
port_context:
required: true
description: >-
Port's payload, including details for who triggered the action and
general context (blueprint, run id, etc...)
jobs:
lock-service-in-port:
runs-on: ubuntu-latest
steps:
- name: Inform execution of request to lock service in Port
uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
baseUrl: https://api.getport.io
operation: PATCH_RUN
runId: ${{fromJson(inputs.port_context).runId}}
logMessage: "About to lock a service in ${{ github.event.inputs.environment }} environment ..."

- name: Lock Service in Production
id: lock-prod-service
if: ${{ github.event.inputs.environment == 'Production' }}
uses: port-labs/port-github-action@v1
with:
identifier: ${{ fromJson(github.event.inputs.port_context).entity.identifier }}
title: ${{ fromJson(github.event.inputs.port_context).entity.title }}
blueprint: ${{ fromJson(github.event.inputs.port_context).blueprint }}
properties: |-
{
"locked_in_prod": true,
"locked_reason_prod": "${{ github.event.inputs.reason }}"
}
relations: "{}"
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
baseUrl: https://api.getport.io
operation: UPSERT
runId: ${{fromJson(inputs.port_context).runId}}

- name: Lock Service in Development
id: lock-test-service
if: ${{ github.event.inputs.environment == 'Development' }}
uses: port-labs/port-github-action@v1
with:
identifier: ${{ fromJson(github.event.inputs.port_context).entity.indentifier }}
title: ${{ fromJson(github.event.inputs.port_context).entity.title }}
blueprint: ${{ fromJson(github.event.inputs.port_context).blueprint }}
properties: |-
{
"locked_in_test": true,
"locked_reason_test": "${{ github.event.inputs.reason }}"
}
relations: "{}"
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
baseUrl: https://api.getport.io
operation: UPSERT
runId: ${{fromJson(inputs.port_context).runId}}

- name: Send Slack Announcement
if: ${{ steps.lock-prod-service.outcome == 'success' || steps.lock-test-service.outcome == 'success' }}
id: slack
uses: slackapi/slack-github-action@v1.25.0
with:
channel-id: '${{ secrets.SLACK_CHANNEL_ID }}'
slack-message: "*Port Service Locked*\n\n*Service Name*: ${{ fromJson(github.event.inputs.port_context).entity.title }}\n*Link*: https://app.getport.io/${{ fromJson(github.event.inputs.port_context).blueprint }}Entity?identifier=${{ fromJson(github.event.inputs.port_context).entity.identifier }}\n*Environment*: ${{ github.event.inputs.environment }}\n*Reporter*: ${{ fromJson(github.event.inputs.port_context).trigger.by.user.email }}\n*Reason*: ${{ github.event.inputs.reason }}"
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}

- name: Inform Port about outcome of sending slack alert
if: ${{ steps.lock-prod-service.outcome == 'success' || steps.lock-test-service.outcome == 'success' }}
uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
baseUrl: https://api.getport.io
operation: PATCH_RUN
runId: ${{fromJson(inputs.port_context).runId}}
logMessage: "The lock operation has been completed successfully and the details is being broadcasted to Slack. The outcome of the Slack announcement is ${{ steps.slack.outcome }}"

- name: Inform unsuccessful service locking in Port
if: ${{ (steps.lock-prod-service.outcome != 'success' && steps.lock-prod-service.outcome != 'skipped') || (steps.lock-test-service.outcome != 'success' && steps.lock-test-service.outcome != 'skipped') }}
uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
baseUrl: https://api.getport.io
operation: PATCH_RUN
runId: ${{fromJson(inputs.port_context).runId}}
logMessage: The attempt to lock the service was not successful

  1. Create another workflow file under .github/workflows/unlock-service.yml with the following content:
GitHub workflow script to unlock a service (click to expand)
unlock-service.yml
name: Unlock Service in Port
on:
workflow_dispatch:
inputs:
environment:
type: string
required: true
reason:
type: string
required: true
port_context:
required: true
description: >-
Port's payload, including details for who triggered the action and
general context (blueprint, run id, etc...)
jobs:
unlock-service-in-port:
runs-on: ubuntu-latest
steps:
- name: Inform execution of request to unlock service in Port
uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
baseUrl: https://api.getport.io
operation: PATCH_RUN
runId: ${{fromJson(inputs.port_context).runId}}
logMessage: "About to unlock a service in ${{ github.event.inputs.environment }} environment ..."

- name: Unlock Service in Production
id: unlock-prod-service
if: ${{ github.event.inputs.environment == 'Production' }}
uses: port-labs/port-github-action@v1
with:
identifier: ${{ fromJson(github.event.inputs.port_context).entity.identifier }}
title: ${{ fromJson(github.event.inputs.port_context).entity.title }}
blueprint: ${{ fromJson(github.event.inputs.port_context).blueprint }}
properties: |-
{
"locked_in_prod": false,
"locked_reason_prod": "${{ github.event.inputs.reason }}"
}
relations: "{}"
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
baseUrl: https://api.getport.io
operation: UPSERT
runId: ${{fromJson(inputs.port_context).runId}}

- name: Unlock Service in Development
id: unlock-test-service
if: ${{ github.event.inputs.environment == 'Development' }}
uses: port-labs/port-github-action@v1
with:
identifier: ${{ fromJson(github.event.inputs.port_context).entity.identifier }}
title: ${{ fromJson(github.event.inputs.port_context).entity.title }}
blueprint: ${{ fromJson(github.event.inputs.port_context).blueprint }}
properties: |-
{
"locked_in_test": false,
"locked_reason_test": "${{ github.event.inputs.reason }}"
}
relations: "{}"
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
baseUrl: https://api.getport.io
operation: UPSERT
runId: ${{fromJson(inputs.port_context).runId}}

- name: Send Slack Announcement
if: ${{ steps.unlock-prod-service.outcome == 'success' || steps.unlock-test-service.outcome == 'success' }}
id: slack
uses: slackapi/slack-github-action@v1.25.0
with:
channel-id: '${{ secrets.SLACK_CHANNEL_ID }}'
slack-message: "*Port Service Unlocked*\n\n*Service Name*: ${{ fromJson(github.event.inputs.port_context).entity.title }}\n*Link*: https://app.getport.io/${{ fromJson(github.event.inputs.port_context).blueprint }}Entity?identifier=${{ fromJson(github.event.inputs.port_context).entity.identifier }}\n*Environment*: ${{ github.event.inputs.environment }}\n*Reporter*: ${{ fromJson(github.event.inputs.port_context).trigger.by.user.email }}\n*Reason*: ${{ github.event.inputs.reason }}"
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}

- name: Inform Port about outcome of sending slack alert
if: ${{ steps.unlock-prod-service.outcome == 'success' || steps.unlock-test-service.outcome == 'success' }}
uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
baseUrl: https://api.getport.io
operation: PATCH_RUN
runId: ${{fromJson(inputs.port_context).runId}}
status: 'SUCCESS'
logMessage: "The unlock operation has been completed successfully and the details is being broadcasted to Slack. The outcome of the Slack announcement is ${{ steps.slack.outcome }}"

- name: Inform unsuccessful service unlocking in Port
if: ${{ (steps.unlock-prod-service.outcome != 'success' && steps.unlock-prod-service.outcome != 'skipped') || (steps.unlock-test-service.outcome != 'success' && steps.unlock-test-service.outcome != 'skipped') }}
uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
baseUrl: https://api.getport.io
operation: PATCH_RUN
runId: ${{fromJson(inputs.port_context).runId}}
status: 'FAILURE'
logMessage: The attempt to unlock the service was not successful

  1. Trigger the actions from the self-service page of your Port application.

Below is the result of a successful service lock and unlock alert sent to a Slack channel:

More relevant guides and examples