diff --git a/docs/guides/all/lock-and-unlock-service-in-port.md b/docs/guides/all/_lock-and-unlock-service-in-port.md similarity index 100% rename from docs/guides/all/lock-and-unlock-service-in-port.md rename to docs/guides/all/_lock-and-unlock-service-in-port.md diff --git a/docs/guides/all/lock-and-unlock-services-in-port.md b/docs/guides/all/lock-and-unlock-services-in-port.md new file mode 100644 index 0000000000..46c4c182b7 --- /dev/null +++ b/docs/guides/all/lock-and-unlock-services-in-port.md @@ -0,0 +1,470 @@ +--- +sidebar_position: 8 +displayed_sidebar: null +description: Learn how to implement comprehensive service locking in Port with self-service actions and automated deployment checks in GitHub workflows. +--- + +import Tabs from "@theme/Tabs" +import TabItem from "@theme/TabItem" +import PortTooltip from "/src/components/tooltip/tooltip.jsx" +import PortApiRegionTip from "/docs/generalTemplates/_port_region_parameter_explanation_template.md" + +# 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](/getting-started/overview). +- Access to your Port organization with permissions to create blueprints and self-service actions. +- [Port's GitHub app](https://github.com/apps/getport-io) needs to be installed (required for GitHub workflow implementation). +- [Slack webhook URL](https://api.slack.com/messaging/webhooks) 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](https://app.getport.io/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: + + ```json showLineNumbers + "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](https://app.getport.io/self-serve) 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) + + ```json showLineNumbers + { + "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) + + ```json showLineNumbers + { + "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](https://app.getport.io/settings/automations) page. + +2. Click on the `+ Automation` button. + +3. Copy and paste the following JSON configuration into the editor. + + :::info Slack webhook URL + Replace `` in the automation definition with your actual Slack webhook URL. + ::: + +
+ Slack notification automation (click to expand) + + ```json showLineNumbers + { + "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": "", + "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**](https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions#creating-secrets-for-a-repository) and add the following secrets: +- `PORT_CLIENT_ID` - Your port `client id` [How to get the credentials](https://docs.port.io/build-your-software-catalog/sync-data-to-catalog/api/#find-your-port-credentials). +- `PORT_CLIENT_SECRET` - Your port `client secret` [How to get the credentials](https://docs.port.io/build-your-software-catalog/sync-data-to-catalog/api/#find-your-port-credentials). + +

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) + +```yml showLineNumbers title="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 +``` + + +
+ + +## Let's test it! + +1. **Lock a service**: + - Head to the [self-service page](https://app.getport.io/self-serve) 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: + + + + + diff --git a/docs/guides/all/service-lock-github-workflow.md b/docs/guides/all/service-lock-github-workflow.md deleted file mode 100644 index b3839e9a50..0000000000 --- a/docs/guides/all/service-lock-github-workflow.md +++ /dev/null @@ -1,239 +0,0 @@ ---- -sidebar_position: 8 -displayed_sidebar: null -description: Learn how to implement service locks in GitHub workflows using Port, ensuring secure and controlled deployment processes. ---- - -import Tabs from "@theme/Tabs" -import TabItem from "@theme/TabItem" -import PortTooltip from "/src/components/tooltip/tooltip.jsx" -import PortApiRegionTip from "/docs/generalTemplates/_port_region_parameter_explanation_template.md" - -# Lock service deployment - -During peak periods such as campaigns, holidays, or system outages, it becomes crucial to maintain stability and avoid unexpected changes or disruptions to services. Implementing a service locking mechanism using [Port's Github Action](http://docs.port.io/build-your-software-catalog/sync-data-to-catalog/api/ci-cd/github-workflow/) helps to enforce this stability by temporarily preventing deployments during these critical times. - - -The CI/CD deployment check described in this guide will follow these steps: - -1. New code is pushed to the `main` branch of a Git repository -2. A [GitHub workflow](https://docs.github.com/en/actions/using-workflows) is triggered by the push event -3. The Github workflow queries Port's Entity API and returns a response for the service -4. If the value of the `locked_in_prod` field is `true`, the deployment check will fail -5. If the value of the `locked_in_prod` field is `false`, the deployment check will succeed - -## Prerequisites -:::info Prerequisites - -- This guide assumes you have a Port account and that you have finished the [onboarding process](/getting-started/overview) -- You will need a GitHub repository in which you can trigger a workflow that we will use in this guide -::: - -Below you can find the JSON for the `Service` blueprint required for the guide: -
-Service blueprint (click to expand) - -```json showLineNumbers -{ - "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", - "Experimental", - "Deprecated" - ], - "enumColors": { - "Production": "green", - "Experimental": "yellow", - "Deprecated": "red" - }, - "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" - } - }, - "required": [] - }, - "mirrorProperties": {}, - "calculationProperties": {}, - "aggregationProperties": {}, - "relations": {} -} -``` -
- -:::note Lock property -Our Service blueprint has a property called `locked_in_prod` with a boolean value. We will use the value of this field to determine whether new deployments of the service are allowed. -::: - -Now that you have your blueprint created, let's manually create a `Notification Service` entity in our software catalog: - -```json showLineNumbers -{ - "identifier": "notification-service", - "title": "Notification Service", - "icon": "Github", - "properties": { - "url": "https://github.com/my-repo", - "language": "Python", - "slack": "https://app.slack.com/client", - "type": "Backend", - "lifecycle": "Production", - "locked_in_prod": true - }, - "relations": {} -} -``` - -## Reading the lock status during deployment - -In order to use the `locked_in_prod` field in your CI/CD pipeline, you will use Port's [GitHub Action](/build-your-software-catalog/custom-integration/api/ci-cd/github-workflow/). - - -Let's go ahead and create a GitHub workflow file in a GitHub repository meant for the `Notification Service`: - -- Create a GitHub repository (or use an existing one) -- Create a `.github` directory - - Inside it create a `workflows` directory - -Inside the `/.github/workflows` directory create a file called `check-service-lock.yml` with the following content: - -
- GitHub workflow configuration (click to expand) - -```yml showLineNumbers -name: Check Service Lock Status -on: - push: - branches: - - "main" -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 - blueprint: service - operation: GET - check-lock-status: - runs-on: ubuntu-latest - needs: get-entity - steps: - - name: Get entity lock status - run: echo "LOCK_STATUS=$(echo '${{needs.get-entity.outputs.entity}}' | jq -r .properties.locked_in_prod)" >> $GITHUB_ENV - - name: Check lock status ๐Ÿšง - if: ${{ env.LOCK_STATUS == 'true' }} - run: | - echo "Service in production is locked, stopping deployment" - exit 1 - run-deployment: - runs-on: ubuntu-latest - needs: [check-lock-status] - steps: - - name: Run deployment - run: echo "Service in production is not locked, continuing deployment" -``` - - -
- -:::tip Secure your credentials -For security reasons it is recommended to save the `PORT_CLIENT_ID` and `PORT_CLIENT_SECRET` as [GitHub Secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets), and access them as shown in the example above. -::: - -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 will look at the step that failed, you will see that the failure is due to the value of the locked field: - - - -If you set the value of the `locked_in_prod` field to `false`, the workflow will perform the deployment without any issue: - - - - -## More relevant guides and examples - -- [Port's self-service action to lock and unlock a service](/guides/all/lock-and-unlock-service-in-port) diff --git a/docs/integrations-index.md b/docs/integrations-index.md index ec50a44655..b57e6b3856 100644 --- a/docs/integrations-index.md +++ b/docs/integrations-index.md @@ -28,10 +28,10 @@ This page contains a list of Port's available integrations, organized by the pla - [Deploy Azure resources using Terraform](/guides/all/create-azure-resource.md) - [Create GitHub secret using GitHub workflows](/guides/all/create-github-secret.md) - [Script to ingest GitHub packages](https://github.com/port-labs/example-github-packages) -- [Lock service deployment](/guides/all/service-lock-github-workflow) +- [Lock service deployment](/guides/all/lock-and-unlock-services-in-port) - [Nudge PR reviewers](/guides/all/nudge-pr-reviewers) - [Promote to production](/guides/all/promote-to-production) -- [Self-service action to lock and unlock a service](/guides/all/lock-and-unlock-service-in-port) +- [Self-service action to lock and unlock a service](/guides/all/lock-and-unlock-services-in-port) - [Connect GitHub Codeowners with Service, Team and User](/guides/all/connect-github-codeowners-with-service-team-and-user) - [Ingest Javascript packages into your catalog using GitHub file ingesting feature](/guides/all/ingest-javascript-packages-into-your-catalog) diff --git a/src/components/guides-section/consts.js b/src/components/guides-section/consts.js index 32a73d61f3..f9d41f59d0 100644 --- a/src/components/guides-section/consts.js +++ b/src/components/guides-section/consts.js @@ -133,14 +133,6 @@ export const availableGuides = [ // category: "Getting started", link: "/guides/all/iam-permissions-guide", }, - { - title: "Lock service deployment", - description: "Implement a service locking mechanism using Port's GitHub Action", - tags: ["SDLC", "Actions"], - logos: ["GitHub"], - // category: "Getting started", - link: "/guides/all/service-lock-github-workflow", - }, { title: "Automate Slack alerts for Overdue PRs", description: "Send a Slack notification for pull requests that have been open longer than a specified time using an automation", @@ -330,12 +322,12 @@ export const availableGuides = [ link: "/guides/all/broadcast-api-consumers-message", }, { - title: "Lock and Unlock Service", + title: "Lock and Unlock Services", description: "Create a self-service action that locks and unlocks a service", tags: ["SDLC", "Actions", "GitHub"], logos: ["GitHub"], // category: "Getting started", - link: "/guides/all/lock-and-unlock-service-in-port", + link: "/guides/all/lock-and-unlock-services-in-port", }, { title: "Create Slack channel for Incident Management", @@ -1286,4 +1278,3 @@ export const availableGuides = [ link: "/guides/all/add-context-to-incident-manager-ai-agent", } ] -