Skip to main content

Check out Port for yourselfย 

Lock and unlock services

Overviewโ€‹

This guide demonstrates a comprehensive service locking mechanism in Port that allows you to lock and unlock services during critical periods while ensuring deployment safety through automated checks.

We will leverage on two approaches for implementing the service locking and unlocking functionality, depending on your needs:

  1. Basic locking without deployment checks: Uses self-service actions with synced webhooks to update service lock status directly.

  2. Advanced locking with deployment checks: Integrates with your CI/CD pipeline (GitHub workflows) to automatically check service lock status before deployments.

Common use casesโ€‹

  • Maintenance & Critical Events: Lock deployment during maintenance, peak traffic periods (campaigns, holidays), or critical events to maintain stability
  • Emergency Situations: Lock deployment in emergencies such as security breaches or vulnerabilities to mitigate risks and prevent further issues
  • Post-Incident Fixes: Unlock deployment to allow teams to implement necessary fixes or updates swiftly and restore system functionality
  • Automated Compliance: Ensure deployment policies are enforced automatically through CI/CD pipelines

Prerequisitesโ€‹

  • Complete the onboarding process.
  • Access to your Port organization with permissions to create blueprints and self-service actions.
  • Port's GitHub app needs to be installed (required for GitHub workflow implementation).
  • Slack webhook URL for notifications (optional).

Set up data modelโ€‹

The service blueprint that was created for you as part of the onboarding process will need to be extended with additional properties for managing lock states across different environments.

Update the service blueprint

  1. Head to the data model page.

  2. Click on the Service blueprint.

  3. Click on the {...} Edit JSON button.

  4. Copy and paste the following JSON configuration into the editor.

    Additional properties to add (click to expand)

    Add the following properties to the existing properties section of your service blueprint:

    "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"
    },
    "trigger_type": {
    "icon": "DefaultProperty",
    "title": "Lock or Unlock",
    "type": "string"
    },
    "triggered_environment": {
    "icon": "DefaultProperty",
    "title": "Triggered Environment",
    "type": "string"
    }
  5. Click Save.

Implementationโ€‹

Lock services without deployment checksโ€‹

We will implement the service locking and unlocking functionality using Port's synced webhooks in self-service actions and an automation to update the service entities.

Follow these steps to set it up:

Create self-service actions

Follow these steps to create the lock and unlock actions:

  1. Head to the self-service page.

  2. Click on the + New Action button.

  3. Click on the {...} Edit JSON button.

  4. Copy and paste the following JSON configuration for the lock action.

    Lock Service action (click to expand)
    {
    "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": ["reason", "environment"],
    "order": [
    "reason",
    "environment"
    ]
    },
    "blueprintIdentifier": "service"
    },
    "invocationMethod": {
    "type": "UPSERT_ENTITY",
    "blueprintIdentifier": "service",
    "mapping": {
    "identifier": "{{ .entity.identifier }}",
    "title": "{{ .entity.title }}",
    "properties": {
    "{{ if .inputs.environment == 'Production' then 'locked_in_prod' else 'locked_in_test' end }}": true,
    "{{ if .inputs.environment == 'Production' then 'locked_reason_prod' else 'locked_reason_test' end }}": "{{ .inputs.reason }}",
    "trigger_type": "Locked",
    "triggered_environment": "{{ .inputs.environment }}"
    }
    }
    },
    "requiredApproval": false
    }
  5. Click Save.

  6. Create another action for unlocking services with the following JSON configuration:

    Unlock service action (click to expand)
    {
    "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": ["reason", "environment"],
    "order": [
    "reason",
    "environment"
    ]
    },
    "blueprintIdentifier": "service"
    },
    "invocationMethod": {
    "type": "UPSERT_ENTITY",
    "blueprintIdentifier": "service",
    "mapping": {
    "identifier": "{{ .entity.identifier }}",
    "title": "{{ .entity.title }}",
    "properties": {
    "{{ if .inputs.environment == 'Production' then 'locked_in_prod' else 'locked_in_test' end }}": false,
    "{{ if .inputs.environment == 'Production' then 'locked_reason_prod' else 'locked_reason_test' end }}": "{{ .inputs.reason }}",
    "trigger_type": "Unlocked",
    "triggered_environment": "{{ .inputs.environment }}"
    }
    }
    },
    "requiredApproval": false
    }
  7. Click Save.

Set up Slack notifications

Create an automation that sends Slack notifications when service lock status changes.

  1. Head to the automation page.

  2. Click on the + Automation button.

  3. Copy and paste the following JSON configuration into the editor.

    Slack webhook URL

    Replace <Your Generated Slack Webhook> in the automation definition with your actual Slack webhook URL.

    Slack notification automation (click to expand)
    {
    "identifier": "serviceLockStatusChange",
    "title": "Notify Slack on Service Lock Status Change",
    "icon": "Slack",
    "description": "Sends a Slack message when the service lock status changes.",
    "trigger": {
    "type": "automation",
    "event": {
    "type": "ENTITY_UPDATED",
    "blueprintIdentifier": "service"
    },
    "condition": {
    "type": "JQ",
    "expressions": [
    ".diff.after.properties.locked_in_prod != .diff.before.properties.locked_in_prod or .diff.after.properties.locked_in_test != .diff.before.properties.locked_in_test"
    ],
    "combinator": "or"
    }
    },
    "invocationMethod": {
    "type": "WEBHOOK",
    "url": "<Your Generated Slack Webhook>",
    "agent": false,
    "synchronized": true,
    "body": {
    "text": "*Port Service {{ .event.diff.after.properties.trigger_type }}*\n\n*Service Name*: {{ .event.diff.after.title }}\n*Link*: https://app.getport.io/{{ .event.context.blueprintIdentifier }}Entity?identifier={{ .event.context.entityIdentifier }}\n\n*Environment:* {{ .event.diff.after.properties.triggered_environment }}\n*Reason:* {{ if .event.diff.after.properties.triggered_environment == 'Production' then .event.diff.after.properties.locked_reason_prod else .event.diff.after.properties.locked_reason_test end }}"
    }
    },
    "publish": true
    }
  4. Click Save.

Lock services with deployment checksโ€‹

This option integrates with a CI/CD pipeline to check the lock status of a service before deployment.
We leverge on Port's Run Github workflow backend in the self-service action to perform the lock/unlock operation.
This approach provides comprehensive deployment protection by blocking builds when services are locked.

Follow these steps to set it up:

Add GitHub secrets

In your GitHub repository, go to Settings > Secrets and add the following secrets:

Add GitHub workflow

Create the file .github/workflows/check-service-lock.yml in the .github/workflows folder of your repository.

GitHub workflow configuration (click to expand)
check-service-lock.yml
name: Check Service Lock Status Before Deployment
on:
push:
branches:
- "main"
pull_request:
branches:
- "main"
types: [opened, synchronize, reopened]

jobs:
get-entity:
runs-on: ubuntu-latest
outputs:
entity: ${{ steps.port-github-action.outputs.entity }}
steps:
- id: port-github-action
name: Get entity from Port
uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
baseUrl: https://api.getport.io
identifier: notification-service # Replace with your service identifier
blueprint: service
operation: GET

check-production-lock:
runs-on: ubuntu-latest
needs: get-entity
if: github.ref == 'refs/heads/main'
steps:
- name: Get production lock status
run: echo "PROD_LOCK_STATUS=$(echo '${{needs.get-entity.outputs.entity}}' | jq -r .properties.locked_in_prod)" >> $GITHUB_ENV

- name: Get production lock reason
run: echo "PROD_LOCK_REASON=$(echo '${{needs.get-entity.outputs.entity}}' | jq -r .properties.locked_reason_prod)" >> $GITHUB_ENV

- name: Check production lock status ๐Ÿšง
if: ${{ env.PROD_LOCK_STATUS == 'true' }}
run: |
echo "โŒ Service is locked in production"
echo "๐Ÿ”’ Lock reason: ${{ env.PROD_LOCK_REASON }}"
echo "๐Ÿ›‘ Deployment blocked until service is unlocked"
exit 1

- name: Production lock check passed โœ…
if: ${{ env.PROD_LOCK_STATUS == 'false' }}
run: |
echo "โœ… Service is not locked in production"
echo "๐Ÿš€ Ready to proceed with deployment"

check-test-lock:
runs-on: ubuntu-latest
needs: get-entity
if: github.event_name == 'pull_request'
steps:
- name: Get test environment lock status
run: echo "TEST_LOCK_STATUS=$(echo '${{needs.get-entity.outputs.entity}}' | jq -r .properties.locked_in_test)" >> $GITHUB_ENV

- name: Get test environment lock reason
run: echo "TEST_LOCK_REASON=$(echo '${{needs.get-entity.outputs.entity}}' | jq -r .properties.locked_reason_test)" >> $GITHUB_ENV

- name: Check test environment lock status ๐Ÿšง
if: ${{ env.TEST_LOCK_STATUS == 'true' }}
run: |
echo "โŒ Service is locked in test environment"
echo "๐Ÿ”’ Lock reason: ${{ env.TEST_LOCK_REASON }}"
echo "๐Ÿ›‘ Test deployment blocked until service is unlocked"
exit 1

- name: Test environment lock check passed โœ…
if: ${{ env.TEST_LOCK_STATUS == 'false' }}
run: |
echo "โœ… Service is not locked in test environment"
echo "๐Ÿš€ Ready to proceed with test deployment"

run-production-deployment:
runs-on: ubuntu-latest
needs: [check-production-lock]
if: github.ref == 'refs/heads/main'
steps:
- name: Deploy to Production ๐Ÿš€
run: |
echo "๐Ÿš€ Deploying service to production environment"
echo "โœ… Production deployment completed successfully"
# Add your actual deployment commands here

run-test-deployment:
runs-on: ubuntu-latest
needs: [check-test-lock]
if: github.event_name == 'pull_request'
steps:
- name: Deploy to Test Environment ๐Ÿงช
run: |
echo "๐Ÿงช Deploying service to test environment"
echo "โœ… Test deployment completed successfully"
# Add your actual test deployment commands here
Selecting a Port API URL by account region

The port_region, port.baseUrl, portBaseUrl, port_base_url and OCEAN__PORT__BASE_URL parameters are used to select which instance or Port API will be used.

Port exposes two API instances, one for the EU region of Port, and one for the US region of Port.

Let's test it!โ€‹

  1. Lock a service:

    • Head to the self-service page and click on the Lock Service action.
    • Choose the service you want to lock and enter a reason (e.g., "Maintenance window for database upgrade")
    • Select the environment (Production, Staging, or Development) and click Execute
  2. Verify the lock works:

    • Check your Slack channel for the lock notification (if configured)
    • If using the GitHub workflow approach, try pushing code to your repository - the workflow should fail with a lock message.
  3. Test unlocking:

    • Execute the Unlock Service action to unlock the service.
    • Check your Slack channel for the unlock notification and try pushing code again - the workflow should now succeed.

Here are some visual examples of the workflow in action:

When a service is locked:

If you try to push code to your repository when the locked_in_prod field is set to true, the deployment workflow will stop:

When you look at the step that failed, you will see that the failure is due to the value of the locked field:

When a service is unlocked:

If you set the value of the locked_in_prod field to false, the workflow will perform the deployment without any issue:

Slack notifications:

Below is the result of successful service lock and unlock alerts sent to a Slack channel after triggering the actions: