commit
e5839a5f9d
6 changed files with 214 additions and 118 deletions
191
.drone.yml
191
.drone.yml
|
|
@ -158,17 +158,10 @@ trigger:
|
|||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: OSS Release
|
||||
name: OSS plugin release
|
||||
|
||||
steps:
|
||||
- name: Check Promote
|
||||
image: alpine
|
||||
commands:
|
||||
- if [ -z "$DRONE_DEPLOY_TO" ]; then echo "Missing DRONE_DEPLOY_TO (Target)"; exit 1; fi
|
||||
- if [ -z "$DRONE_TAG" ]; then echo "Missing DRONE_TAG"; exit 1; fi
|
||||
- echo Promoting $DRONE_TAG to $DRONE_DEPLOY_TO
|
||||
|
||||
- name: Build Plugin
|
||||
- name: build plugin
|
||||
image: node:14.6.0-stretch
|
||||
commands:
|
||||
- apt-get update
|
||||
|
|
@ -178,23 +171,14 @@ steps:
|
|||
- yarn --network-timeout 500000
|
||||
- yarn build
|
||||
- ls ./
|
||||
depends_on:
|
||||
- Check Promote
|
||||
when:
|
||||
event:
|
||||
- promote
|
||||
target:
|
||||
- oss
|
||||
ref:
|
||||
- refs/tags/v*.*.*
|
||||
|
||||
- name: Sign and Package Plugin
|
||||
- name: sign and package plugin
|
||||
image: node:14.6.0-stretch
|
||||
environment:
|
||||
GRAFANA_API_KEY:
|
||||
from_secret: gcom_plugin_publisher_api_key
|
||||
depends_on:
|
||||
- Build Plugin
|
||||
- build plugin
|
||||
commands:
|
||||
- apt-get update
|
||||
- apt-get install zip
|
||||
|
|
@ -206,7 +190,7 @@ steps:
|
|||
- zip -r grafana-oncall-app.zip ./grafana-oncall-app
|
||||
- if [ -z "$DRONE_TAG" ]; then echo "No tag, skipping archive"; else cp grafana-oncall-app.zip grafana-oncall-app-${DRONE_TAG}.zip; fi
|
||||
|
||||
- name: Publish Plugin to grafana.com (release)
|
||||
- name: publish plugin to grafana.com (release)
|
||||
image: curlimages/curl:7.73.0
|
||||
environment:
|
||||
GRAFANA_API_KEY:
|
||||
|
|
@ -214,95 +198,36 @@ steps:
|
|||
commands:
|
||||
- "curl -f -s -H \"Authorization: Bearer $${GRAFANA_API_KEY}\" -d \"download[any][url]=https://storage.googleapis.com/grafana-oncall-app/releases/grafana-oncall-app-${DRONE_TAG}.zip\" -d \"download[any][md5]=$$(curl -sL https://storage.googleapis.com/grafana-oncall-app/releases/grafana-oncall-app-${DRONE_TAG}.zip | md5sum | cut -d' ' -f1)\" -d url=https://github.com/grafana/oncall/grafana-plugin https://grafana.com/api/plugins"
|
||||
depends_on:
|
||||
- Sign and Package Plugin
|
||||
|
||||
- name: Image Tag
|
||||
image: alpine
|
||||
commands:
|
||||
- apk add --no-cache bash git sed
|
||||
- git fetch origin --tags
|
||||
- chmod +x ./tools/image-tag.sh
|
||||
- echo $(./tools/image-tag.sh)
|
||||
- echo $(./tools/image-tag.sh) > .tags
|
||||
- if [ -z "$DRONE_TAG" ]; then echo "No tag, not modifying version"; else sed "0,/VERSION.*/ s/VERSION.*/VERSION = \"${DRONE_TAG}\"/g" engine/settings/base.py > engine/settings/base.temp && mv engine/settings/base.temp engine/settings/base.py; fi
|
||||
- cat engine/settings/base.py | grep VERSION | head -1
|
||||
depends_on:
|
||||
- Check Promote
|
||||
when:
|
||||
event:
|
||||
- promote
|
||||
target:
|
||||
- oss
|
||||
ref:
|
||||
- refs/tags/v*.*.*
|
||||
|
||||
- name: Build and Push Engine Docker Image Backend to Dockerhub
|
||||
image: plugins/docker
|
||||
settings:
|
||||
repo: grafana/oncall
|
||||
dockerfile: engine/Dockerfile
|
||||
context: engine/
|
||||
password:
|
||||
from_secret: docker_password
|
||||
username:
|
||||
from_secret: docker_username
|
||||
depends_on:
|
||||
- Image Tag
|
||||
|
||||
- name: Unrecognized Promote Target
|
||||
image: alpine
|
||||
commands:
|
||||
- echo $DRONE_DEPLOY_TO is not a recognized promote target!
|
||||
- exit 1
|
||||
when:
|
||||
target:
|
||||
exclude:
|
||||
- oss
|
||||
- sign and package plugin
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- promote
|
||||
target:
|
||||
- oss
|
||||
ref:
|
||||
- refs/tags/v*.*.*
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: OSS Release (arm64)
|
||||
name: OSS engine release (amd64)
|
||||
platform:
|
||||
os: linux
|
||||
arch: arm64
|
||||
|
||||
arch: amd64
|
||||
steps:
|
||||
- name: Check Promote
|
||||
- name: set engine version
|
||||
image: alpine
|
||||
commands:
|
||||
- if [ -z "$DRONE_DEPLOY_TO" ]; then echo "Missing DRONE_DEPLOY_TO (Target)"; exit 1; fi
|
||||
- if [ -z "$DRONE_TAG" ]; then echo "Missing DRONE_TAG"; exit 1; fi
|
||||
- echo Promoting $DRONE_TAG to $DRONE_DEPLOY_TO
|
||||
|
||||
- name: Image Tag
|
||||
image: alpine
|
||||
commands:
|
||||
- apk add --no-cache bash git sed
|
||||
- git fetch origin --tags
|
||||
- chmod +x ./tools/image-tag.sh
|
||||
- echo $(./tools/image-tag.sh)
|
||||
- echo $(./tools/image-tag.sh) > .tags
|
||||
- apk add --no-cache bash sed
|
||||
- if [ -z "$DRONE_TAG" ]; then echo "No tag, not modifying version"; else sed "0,/VERSION.*/ s/VERSION.*/VERSION = \"${DRONE_TAG}\"/g" engine/settings/base.py > engine/settings/base.temp && mv engine/settings/base.temp engine/settings/base.py; fi
|
||||
- cat engine/settings/base.py | grep VERSION | head -1
|
||||
depends_on:
|
||||
- Check Promote
|
||||
when:
|
||||
event:
|
||||
- promote
|
||||
target:
|
||||
- oss
|
||||
ref:
|
||||
- refs/tags/v*.*.*
|
||||
|
||||
- name: Build and Push Engine Docker Image Backend to Dockerhub
|
||||
- name: build and push docker image
|
||||
image: plugins/docker
|
||||
settings:
|
||||
repo: grafana/oncall
|
||||
tags: ${DRONE_TAG}-amd64-linux
|
||||
dockerfile: engine/Dockerfile
|
||||
context: engine/
|
||||
password:
|
||||
|
|
@ -310,21 +235,81 @@ steps:
|
|||
username:
|
||||
from_secret: docker_username
|
||||
depends_on:
|
||||
- Image Tag
|
||||
|
||||
- name: Unrecognized Promote Target
|
||||
image: alpine
|
||||
commands:
|
||||
- echo $DRONE_DEPLOY_TO is not a recognized promote target!
|
||||
- exit 1
|
||||
when:
|
||||
target:
|
||||
exclude:
|
||||
- oss
|
||||
- set engine version
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- promote
|
||||
target:
|
||||
- oss
|
||||
ref:
|
||||
- refs/tags/v*.*.*
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: OSS engine release (arm64)
|
||||
platform:
|
||||
os: linux
|
||||
arch: arm64
|
||||
steps:
|
||||
- name: set engine version
|
||||
image: alpine
|
||||
commands:
|
||||
- apk add --no-cache bash sed
|
||||
- if [ -z "$DRONE_TAG" ]; then echo "No tag, not modifying version"; else sed "0,/VERSION.*/ s/VERSION.*/VERSION = \"${DRONE_TAG}\"/g" engine/settings/base.py > engine/settings/base.temp && mv engine/settings/base.temp engine/settings/base.py; fi
|
||||
- cat engine/settings/base.py | grep VERSION | head -1
|
||||
|
||||
- name: build and push docker image
|
||||
image: plugins/docker
|
||||
settings:
|
||||
repo: grafana/oncall
|
||||
tags: ${DRONE_TAG}-arm64-linux
|
||||
dockerfile: engine/Dockerfile
|
||||
context: engine/
|
||||
password:
|
||||
from_secret: docker_password
|
||||
username:
|
||||
from_secret: docker_username
|
||||
depends_on:
|
||||
- set engine version
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- promote
|
||||
target:
|
||||
- oss
|
||||
ref:
|
||||
- refs/tags/v*.*.*
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: manifest
|
||||
steps:
|
||||
- name: manifest
|
||||
image: plugins/manifest
|
||||
settings:
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
target: grafana/oncall:${DRONE_TAG}
|
||||
template: grafana/oncall:${DRONE_TAG}-ARCH-OS
|
||||
platforms:
|
||||
- linux/amd64
|
||||
- linux/arm64
|
||||
depends_on:
|
||||
- OSS engine release (amd64)
|
||||
- OSS engine release (arm64)
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- promote
|
||||
target:
|
||||
- oss
|
||||
ref:
|
||||
- refs/tags/v*.*.*
|
||||
|
||||
---
|
||||
# Secret for pulling docker images.
|
||||
|
|
@ -397,6 +382,6 @@ kind: secret
|
|||
name: drone_token
|
||||
---
|
||||
kind: signature
|
||||
hmac: 484ec4337e39172f554faafaa93b67ceb9397d2c668ab2eb6f38176e94402aaa
|
||||
hmac: a52e4cea86ef50bbf7b19a05bec619368281e58927206709ef1e9dfd1df44e0e
|
||||
|
||||
...
|
||||
|
|
|
|||
33
DEVELOPER.md
33
DEVELOPER.md
|
|
@ -1,7 +1,9 @@
|
|||
* [Developer quickstart](#developer-quickstart)
|
||||
* [Code style](#code-style)
|
||||
* [Backend setup](#backend-setup)
|
||||
* [Frontend setup](#frontend-setup)
|
||||
* [Slack application setup](#slack-application-setup)
|
||||
* [Update drone build](#update-drone-build)
|
||||
* [Troubleshooting](#troubleshooting)
|
||||
* [ld: library not found for -lssl](#ld-library-not-found-for--lssl)
|
||||
* [Could not build wheels for cryptography which use PEP 517 and cannot be installed directly](#could-not-build-wheels-for-cryptography-which-use-pep-517-and-cannot-be-installed-directly)
|
||||
|
|
@ -131,6 +133,22 @@ extra_hosts:
|
|||
|
||||
For Slack app configuration check our docs: https://grafana.com/docs/grafana-cloud/oncall/open-source/#slack-setup
|
||||
|
||||
|
||||
### Update drone build
|
||||
The .drone.yml build file must be signed when changes are made to it. Follow these steps:
|
||||
|
||||
If you have not installed drone CLI follow [these instructions](https://docs.drone.io/cli/install/)
|
||||
|
||||
To sign the .drone.yml file:
|
||||
```bash
|
||||
export DRONE_SERVER=https://drone.grafana.net
|
||||
|
||||
# Get your drone token from https://drone.grafana.net/account
|
||||
export DRONE_TOKEN=<Your DRONE_TOKEN>
|
||||
|
||||
drone sign --save grafana/oncall .drone.yml
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### ld: library not found for -lssl
|
||||
|
|
@ -241,18 +259,3 @@ pytest -n4
|
|||
5. Create a new Django Server run configuration to Run/Debug the engine
|
||||
- Use a plugin such as EnvFile to load the .env file
|
||||
- Change port from 8000 to 8080
|
||||
|
||||
## Update drone build
|
||||
The .drone.yml build file must be signed when changes are made to it. Follow these steps:
|
||||
|
||||
If you have not installed drone CLI follow [these instructions](https://docs.drone.io/cli/install/)
|
||||
|
||||
To sign the .drone.yml file:
|
||||
```bash
|
||||
export DRONE_SERVER=https://drone.grafana.net
|
||||
|
||||
# Get your drone token from https://drone.grafana.net/account
|
||||
export DRONE_TOKEN=<Your DRONE_TOKEN>
|
||||
|
||||
drone sign --save grafana/oncall .drone.yml
|
||||
```
|
||||
|
|
|
|||
|
|
@ -800,6 +800,30 @@ def test_admin_can_unlink_another_user_backend_account(
|
|||
assert response.status_code == status.HTTP_200_OK
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_admin_can_unlink_another_user_slack_account(
|
||||
make_organization_with_slack_team_identity,
|
||||
make_user_for_organization,
|
||||
make_user_with_slack_user_identity,
|
||||
make_token_for_organization,
|
||||
make_user_auth_headers,
|
||||
):
|
||||
organization, slack_team_identity = make_organization_with_slack_team_identity()
|
||||
admin = make_user_for_organization(organization, role=Role.ADMIN)
|
||||
editor, slack_user_identity_1 = make_user_with_slack_user_identity(
|
||||
slack_team_identity, organization, slack_id="user_1", role=Role.EDITOR
|
||||
)
|
||||
|
||||
_, token = make_token_for_organization(organization)
|
||||
client = APIClient()
|
||||
url = reverse("api-internal:user-unlink-slack", kwargs={"pk": editor.public_primary_key})
|
||||
|
||||
response = client.post(url, format="json", **make_user_auth_headers(admin, token))
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
editor.refresh_from_db()
|
||||
assert editor.slack_user_identity is None
|
||||
|
||||
|
||||
"""Test user permissions"""
|
||||
|
||||
|
||||
|
|
@ -1038,6 +1062,28 @@ def test_user_cant_get_another_user_backend_verification_code(
|
|||
assert response.status_code == status.HTTP_403_FORBIDDEN
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_user_can_unlink_own_slack_account(
|
||||
make_organization_with_slack_team_identity,
|
||||
make_user_with_slack_user_identity,
|
||||
make_token_for_organization,
|
||||
make_user_auth_headers,
|
||||
):
|
||||
organization, slack_team_identity = make_organization_with_slack_team_identity()
|
||||
user, slack_user_identity_1 = make_user_with_slack_user_identity(
|
||||
slack_team_identity, organization, slack_id="user_1", role=Role.EDITOR
|
||||
)
|
||||
|
||||
_, token = make_token_for_organization(organization)
|
||||
client = APIClient()
|
||||
url = reverse("api-internal:user-unlink-slack", kwargs={"pk": user.public_primary_key})
|
||||
|
||||
response = client.post(url, format="json", **make_user_auth_headers(user, token))
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
user.refresh_from_db()
|
||||
assert user.slack_user_identity is None
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_user_can_unlink_backend_own_account(
|
||||
make_organization, make_user_for_organization, make_token_for_organization, make_user_auth_headers
|
||||
|
|
@ -1086,6 +1132,31 @@ def test_user_unlink_backend_backend_account_not_found(
|
|||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_user_cant_unlink_slack_another_user(
|
||||
make_organization_with_slack_team_identity,
|
||||
make_user_with_slack_user_identity,
|
||||
make_token_for_organization,
|
||||
make_user_auth_headers,
|
||||
):
|
||||
organization, slack_team_identity = make_organization_with_slack_team_identity()
|
||||
first_user, slack_user_identity_1 = make_user_with_slack_user_identity(
|
||||
slack_team_identity, organization, slack_id="user_1", role=Role.EDITOR
|
||||
)
|
||||
second_user, slack_user_identity_2 = make_user_with_slack_user_identity(
|
||||
slack_team_identity, organization, slack_id="user_2", role=Role.EDITOR
|
||||
)
|
||||
|
||||
_, token = make_token_for_organization(organization)
|
||||
client = APIClient()
|
||||
url = reverse("api-internal:user-unlink-slack", kwargs={"pk": first_user.public_primary_key})
|
||||
|
||||
response = client.post(url, format="json", **make_user_auth_headers(second_user, token))
|
||||
assert response.status_code == status.HTTP_403_FORBIDDEN
|
||||
first_user.refresh_from_db()
|
||||
assert first_user.slack_user_identity is not None
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_user_cant_unlink_backend__another_user(
|
||||
make_organization, make_user_for_organization, make_token_for_organization, make_user_auth_headers
|
||||
|
|
|
|||
|
|
@ -127,6 +127,7 @@ class UserView(
|
|||
"get_verification_code",
|
||||
"get_backend_verification_code",
|
||||
"get_telegram_verification_code",
|
||||
"unlink_slack",
|
||||
"unlink_telegram",
|
||||
"unlink_backend",
|
||||
"make_test_call",
|
||||
|
|
@ -146,6 +147,7 @@ class UserView(
|
|||
"get_verification_code",
|
||||
"get_backend_verification_code",
|
||||
"get_telegram_verification_code",
|
||||
"unlink_slack",
|
||||
"unlink_telegram",
|
||||
"unlink_backend",
|
||||
"make_test_call",
|
||||
|
|
@ -350,6 +352,20 @@ class UserView(
|
|||
|
||||
return Response({"telegram_code": str(new_code.uuid), "bot_link": bot_link}, status=status.HTTP_200_OK)
|
||||
|
||||
@action(detail=True, methods=["post"])
|
||||
def unlink_slack(self, request, pk):
|
||||
user = self.get_object()
|
||||
user.slack_user_identity = None
|
||||
user.save(update_fields=["slack_user_identity"])
|
||||
write_chatops_insight_log(
|
||||
author=request.user,
|
||||
event_name=ChatOpsEvent.USER_UNLINKED,
|
||||
chatops_type=ChatOpsType.SLACK,
|
||||
linked_user=user.username,
|
||||
linked_user_id=user.public_primary_key,
|
||||
)
|
||||
return Response(status=status.HTTP_200_OK)
|
||||
|
||||
@action(detail=True, methods=["post"])
|
||||
def unlink_telegram(self, request, pk):
|
||||
user = self.get_object()
|
||||
|
|
|
|||
|
|
@ -32,6 +32,10 @@ const SlackConnector = (props: SlackConnectorProps) => {
|
|||
onTabChange(UserSettingsTab.SlackInfo);
|
||||
}, []);
|
||||
|
||||
const handleUnlinkSlackAccount = useCallback(() => {
|
||||
userStore.unlinkSlack(userStore.currentUserPk);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={cx('user-item')}>
|
||||
<Label>Slack username:</Label>
|
||||
|
|
@ -39,6 +43,9 @@ const SlackConnector = (props: SlackConnectorProps) => {
|
|||
{storeUser.slack_user_identity ? (
|
||||
<div>
|
||||
<Text type="secondary"> Slack account is connected</Text>
|
||||
<Button size="sm" fill="text" variant="destructive" onClick={handleUnlinkSlackAccount}>
|
||||
Unlink Slack account
|
||||
</Button>
|
||||
</div>
|
||||
) : teamStore.currentTeam?.slack_team_identity ? (
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -124,6 +124,20 @@ export class UserStore extends BaseStore {
|
|||
return await makeRequest(`/users/${userPk}/get_backend_verification_code/?backend=${backend}`, {});
|
||||
};
|
||||
|
||||
@action
|
||||
unlinkSlack = async (userPk: User['pk']) => {
|
||||
await makeRequest(`/users/${userPk}/unlink_slack/`, {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
const user = await this.getById(userPk);
|
||||
|
||||
this.items = {
|
||||
...this.items,
|
||||
[user.pk]: user,
|
||||
};
|
||||
};
|
||||
|
||||
@action
|
||||
unlinkTelegram = async (userPk: User['pk']) => {
|
||||
await makeRequest(`/users/${userPk}/unlink_telegram/`, {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue