Merge pull request #436 from grafana/dev

Merge dev to main
This commit is contained in:
Vadim Stepanov 2022-08-30 18:48:10 +01:00 committed by GitHub
commit e5839a5f9d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 214 additions and 118 deletions

View file

@ -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
...

View file

@ -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
```

View file

@ -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

View file

@ -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()

View file

@ -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>

View file

@ -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/`, {