Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion src/amg/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -155,4 +155,14 @@ Release History

2.8.1
++++++
* `az grafana create`: block creation for resources of Essential SKU tier
* `az grafana create`: block creation for resources of Essential SKU tier

2.9.0
++++++
* `az grafana notification-channel`: remove deprecated command group due to Grafana legacy alerting deprecation
* `az grafana api-key`: remove deprecated command group; use `az grafana service-account` instead
* `az grafana backup`: GA
* `az grafana restore`: GA
* `az grafana integrations monitor`: GA
* `az grafana data-source update`: switch to UID-based endpoint due to deprecated API
* `az grafana data-source show`: remove data source lookup by id due to deprecated API
Comment thread
ABZhang0 marked this conversation as resolved.
Outdated
8 changes: 0 additions & 8 deletions src/amg/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,6 @@ az grafana data-source create \
--definition ~/data-source-sql.json
```

#### configure a notification channel
*Examples:*
```
az grafana notification-channel create \
-n MyGrafanaInstance \
--definition ~/notification-channel-teams.json
```

#### Create a dashboard
*Examples:*
```
Expand Down
71 changes: 0 additions & 71 deletions src/amg/azext_amg/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,53 +177,6 @@
short-summary: Query a data source having backend implementation
"""

helps['grafana notification-channel'] = """
type: group
short-summary: Commands to manage notification channels of an instance.
long-summary: As part of Grafana legacy alerting, this command group only works with Grafana 10 and below.
"""

helps['grafana notification-channel list'] = """
type: command
short-summary: List all notification channels of an instance.
"""

helps['grafana notification-channel show'] = """
type: command
short-summary: Get the details of a notification channel
"""

helps['grafana notification-channel create'] = """
type: command
short-summary: Create a notification channel.
examples:
- name: create a notification channel for Teams
text: |
az grafana notification-channel create -n MyGrafana --definition '{
"name": "Teams",
"settings": {
"uploadImage": true,
"url": "https://webhook.office.com/IncomingWebhook/"
},
"type": "teams"
}'
"""

helps['grafana notification-channel update'] = """
type: command
short-summary: Update a notification channel.
"""

helps['grafana notification-channel delete'] = """
type: command
short-summary: Delete a notification channel.
"""

helps['grafana notification-channel test'] = """
type: command
short-summary: Test a notification channel.
"""

helps['grafana dashboard'] = """
type: group
short-summary: Commands to manage dashboards of an instance.
Expand Down Expand Up @@ -366,30 +319,6 @@
short-summary: Get the details of a user.
"""

helps['grafana api-key'] = """
type: group
short-summary: Commands to manage API keys.
long-summary: API keys are deprecated by Grafana Labs and will not be supported in Grafana 12 and above. Please use service accounts instead.
"""

helps['grafana api-key create'] = """
type: command
short-summary: Create a new API key.
examples:
- name: Create a new API key.
text: az grafana api-key create -g myResourceGroup -n myGrafana --key myKey
"""

helps['grafana api-key list'] = """
type: command
short-summary: List existing API keys.
"""

helps['grafana api-key delete'] = """
type: command
short-summary: Delete an API key.
"""

helps['grafana service-account'] = """
type: group
short-summary: Commands to manage service accounts.
Expand Down
18 changes: 1 addition & 17 deletions src/amg/azext_amg/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,26 +72,10 @@ def load_arguments(self, _):
c.argument("dashboards_to_include", nargs='+', help="Space separated titles of dashboards to include in sync. Pair with --folders-to-include for folders specific")
c.argument("dashboards_to_exclude", nargs='+', help="Space separated titles of dashboards to exclude in sync. Pair with --folders-to-exclude for folders specific")

with self.argument_context("grafana api-key") as c:
c.argument("key_name", help="api key name")
c.argument("role", grafana_role_type, default="Viewer")
c.argument("time_to_live", default="1d", help="The API key life duration. For example, 1d if your key is going to last fr one day. Supported units are: s,m,h,d,w,M,y")

with self.argument_context("grafana api-key create") as c:
c.argument("key", help="api key name")

with self.argument_context("grafana api-key delete") as c:
c.argument("key", help="id or name that identify an api-key to delete")

with self.argument_context("grafana data-source") as c:
c.argument("data_source", help="name, id, uid which can identify a data source. CLI will search in the order of name, id, and uid, till finds a match")
c.argument("data_source", help="name or uid which can identify a data source. CLI will search in the order of name and uid, till finds a match")
Comment thread
ABZhang0 marked this conversation as resolved.
Outdated
c.argument("definition", type=validate_file_or_dict, help="json string with data source definition, or a path to a file with such content")

with self.argument_context("grafana notification-channel") as c:
c.argument("notification_channel", help="id, uid which can identify a data source. CLI will search in the order of id, and uid, till finds a match")
c.argument("definition", type=validate_file_or_dict, help="json string with notification channel definition, or a path to a file with such content")
c.argument("short", action='store_true', help="list notification channels in short format.")

with self.argument_context("grafana data-source query") as c:
c.argument("conditions", nargs="+", help="space-separated condition in a format of `<name>=<value>`")
c.argument("time_from", options_list=["--from"], help="start time in iso 8601, e.g. '2022-01-02T16:15:00'. Default: 1 hour early")
Expand Down
23 changes: 5 additions & 18 deletions src/amg/azext_amg/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ def load_command_table(self, _):
self.command_table['grafana create'] = GrafanaCreate(loader=self)
self.command_table['grafana delete'] = GrafanaDelete(loader=self)
self.command_table['grafana update'] = GrafanaUpdate(loader=self)
g.custom_command('backup', 'backup_grafana', is_preview=True)
g.custom_command('restore', 'restore_grafana', is_preview=True)
g.custom_command('backup', 'backup_grafana')
g.custom_command('restore', 'restore_grafana')
g.custom_command('migrate', 'migrate_grafana', is_preview=True)

with self.command_group('grafana dashboard') as g:
Expand All @@ -34,14 +34,6 @@ def load_command_table(self, _):
g.custom_command('query', 'query_data_source')
g.custom_command('update', 'update_data_source')

with self.command_group('grafana notification-channel', deprecate_info=self.deprecate()) as g:
g.custom_command('list', 'list_notification_channels')
g.custom_show_command('show', 'show_notification_channel')
g.custom_command('create', 'create_notification_channel')
g.custom_command('update', 'update_notification_channel')
g.custom_command('delete', 'delete_notification_channel')
g.custom_command('test', 'test_notification_channel')

with self.command_group('grafana folder') as g:
g.custom_command('create', 'create_folder')
g.custom_command('list', 'list_folders')
Expand All @@ -54,11 +46,6 @@ def load_command_table(self, _):
g.custom_show_command('show', 'show_user')
g.custom_command('actual-user', 'get_actual_user')

with self.command_group('grafana api-key', deprecate_info=self.deprecate()) as g:
g.custom_command('create', 'create_api_key')
g.custom_command('list', 'list_api_keys')
g.custom_command('delete', 'delete_api_key')

with self.command_group('grafana service-account') as g:
g.custom_command('create', 'create_service_account')
g.custom_command('list', 'list_service_accounts')
Expand All @@ -72,6 +59,6 @@ def load_command_table(self, _):
g.custom_command('delete', 'delete_service_account_token')

with self.command_group('grafana integrations monitor') as g:
g.custom_command('add', 'link_monitor', is_preview=True)
g.custom_command('list', 'list_monitors', is_preview=True)
g.custom_command('delete', 'unlink_monitor', is_preview=True)
g.custom_command('add', 'link_monitor')
g.custom_command('list', 'list_monitors')
g.custom_command('delete', 'unlink_monitor')
121 changes: 15 additions & 106 deletions src/amg/azext_amg/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -492,59 +492,11 @@ def list_data_sources(cmd, grafana_name, resource_group_name=None, api_key_or_to

def update_data_source(cmd, grafana_name, data_source, definition, resource_group_name=None, api_key_or_token=None):
data = _find_data_source(cmd, resource_group_name, grafana_name, data_source, api_key_or_token=api_key_or_token)
response = _send_request(cmd, resource_group_name, grafana_name, "put", "/api/datasources/" + str(data['id']),
response = _send_request(cmd, resource_group_name, grafana_name, "put", "/api/datasources/uid/" + data['uid'],
definition, api_key_or_token=api_key_or_token)
return json.loads(response.content)


def list_notification_channels(cmd, grafana_name, resource_group_name=None, short=False, api_key_or_token=None):
if short is False:
response = _send_request(cmd, resource_group_name, grafana_name, "get", "/api/alert-notifications",
api_key_or_token=api_key_or_token)
else:
response = _send_request(cmd, resource_group_name, grafana_name, "get", "/api/alert-notifications/lookup",
api_key_or_token=api_key_or_token)
return json.loads(response.content)


def show_notification_channel(cmd, grafana_name, notification_channel, resource_group_name=None, api_key_or_token=None):
return _find_notification_channel(cmd, resource_group_name, grafana_name, notification_channel,
api_key_or_token=api_key_or_token)


def create_notification_channel(cmd, grafana_name, definition, resource_group_name=None, api_key_or_token=None):
response = _send_request(cmd, resource_group_name, grafana_name, "post", "/api/alert-notifications", definition,
api_key_or_token=api_key_or_token)
return json.loads(response.content)


def update_notification_channel(cmd, grafana_name, notification_channel, definition, resource_group_name=None,
api_key_or_token=None):
data = _find_notification_channel(cmd, resource_group_name, grafana_name, notification_channel,
api_key_or_token=api_key_or_token)
definition['id'] = data['id']
response = _send_request(cmd, resource_group_name, grafana_name, "put",
"/api/alert-notifications/" + str(data['id']),
definition, api_key_or_token=api_key_or_token)
return json.loads(response.content)


def delete_notification_channel(cmd, grafana_name, notification_channel, resource_group_name=None,
api_key_or_token=None):
data = _find_notification_channel(cmd, resource_group_name, grafana_name, notification_channel,
api_key_or_token=api_key_or_token)
_send_request(cmd, resource_group_name, grafana_name, "delete", "/api/alert-notifications/" + str(data["id"]),
api_key_or_token=api_key_or_token)


def test_notification_channel(cmd, grafana_name, notification_channel, resource_group_name=None, api_key_or_token=None):
data = _find_notification_channel(cmd, resource_group_name, grafana_name, notification_channel,
api_key_or_token=api_key_or_token)
response = _send_request(cmd, resource_group_name, grafana_name, "post", "/api/alert-notifications/test",
data, api_key_or_token=api_key_or_token)
return json.loads(response.content)


def create_folder(cmd, grafana_name, title, parent_folder=None, resource_group_name=None, api_key_or_token=None,
subscription=None):
payload = {
Expand Down Expand Up @@ -611,39 +563,6 @@ def _find_folder(cmd, resource_group_name, grafana_name, folder, api_key_or_toke
return match


def list_api_keys(cmd, grafana_name, resource_group_name=None):
response = _send_request(cmd, resource_group_name, grafana_name, "get",
"/api/auth/keys?includedExpired=false&accesscontrol=true")
return json.loads(response.content)


def delete_api_key(cmd, grafana_name, key, resource_group_name=None):
# Find the key id based on name
try:
int(key)
except ValueError:
# looks like a key name is provided, need to convert to id to delete
keys = list_api_keys(cmd, grafana_name, resource_group_name=resource_group_name)
temp = next((k for k in keys if k['name'].lower() == key.lower()), None)
if temp:
key = str(temp['id'])
_send_request(cmd, resource_group_name, grafana_name, "delete", "/api/auth/keys/" + key)


def create_api_key(cmd, grafana_name, key, role=None, time_to_live=None, resource_group_name=None):
seconds = _convert_duration_to_seconds(time_to_live)

data = {
"name": key,
"role": role,
"secondsToLive": seconds
}
response = _send_request(cmd, resource_group_name, grafana_name, "post", "/api/auth/keys", data)
content = json.loads(response.content)
logger.warning("You will only be able to view this key here once. Please save it in a secure place.")
return content


def _convert_duration_to_seconds(time_to_live):
unit_to_seconds = {
"s": 1,
Expand Down Expand Up @@ -864,32 +783,22 @@ def list_monitors(cmd, grafana_name, resource_group_name=None):


def _find_data_source(cmd, resource_group_name, grafana_name, data_source, api_key_or_token=None):
response = _send_request(cmd, resource_group_name, grafana_name, "get", "/api/datasources/name/" + data_source,
raise_for_error_status=False, api_key_or_token=api_key_or_token)
if response.status_code >= 400:
response = _send_request(cmd, resource_group_name, grafana_name, "get", "/api/datasources/" + data_source,
raise_for_error_status=False, api_key_or_token=api_key_or_token)
if response.status_code >= 400:
response = _send_request(cmd, resource_group_name, grafana_name,
"get", "/api/datasources/uid/" + data_source,
raise_for_error_status=False, api_key_or_token=api_key_or_token)
if response.status_code >= 400:
raise ArgumentUsageError(f"Couldn't found data source {data_source}. Ex: {response.status_code}")
return json.loads(response.content)

# Numeric data source IDs are not supported: the /api/datasources/{id} endpoints
# were deprecated in Grafana 9 and are disabled by default in Grafana 13.
if data_source.isdigit():
raise ArgumentUsageError(
f"Numeric data source IDs ({data_source}) are not supported. "
"Use the data source name or UID instead.")
Comment thread
ABZhang0 marked this conversation as resolved.
Outdated

def _find_notification_channel(cmd, resource_group_name, grafana_name, notification_channel, api_key_or_token=None):
response = _send_request(cmd, resource_group_name, grafana_name, "get",
"/api/alert-notifications/" + notification_channel,
raise_for_error_status=False, api_key_or_token=api_key_or_token)
if response.status_code >= 400:
response = _send_request(cmd, resource_group_name, grafana_name,
"get", "/api/alert-notifications/uid/" + notification_channel,
for path in (f"/api/datasources/name/{data_source}",
f"/api/datasources/uid/{data_source}"):
response = _send_request(cmd, resource_group_name, grafana_name, "get", path,
raise_for_error_status=False, api_key_or_token=api_key_or_token)
if response.status_code >= 400:
raise ArgumentUsageError(
f"Couldn't found notification channel {notification_channel}. Ex: {response.status_code}")
return json.loads(response.content)
if response.status_code < 400:
return json.loads(response.content)

raise ArgumentUsageError(
f"Couldn't find data source {data_source}. Ex: {response.status_code}")


# For UX: we accept a file path for complex payload such as dashboard/data-source definition
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1863,7 +1863,7 @@ interactions:
content-type:
- application/json
method: PUT
uri: https://clitestamge2e000002-gghwdbecfab9hvf0.weu.grafana.azure.com/api/datasources/5
uri: https://clitestamge2e000002-gghwdbecfab9hvf0.weu.grafana.azure.com/api/datasources/uid/da714998-ca29-4adc-851b-1bd93371cbc6
response:
body:
string: '{"datasource":{"id":5,"uid":"da714998-ca29-4adc-851b-1bd93371cbc6","orgId":1,"name":"Test
Expand Down
2 changes: 1 addition & 1 deletion src/amg/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

# TODO: Confirm this is the right version number you want and it matches your
# HISTORY.rst entry.
VERSION = '2.8.1'
VERSION = '2.9.0'

# The full list of classifiers is available at
# https://pypi.python.org/pypi?%3Aaction=list_classifiers
Expand Down
Loading