Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Onboard command extension for Azure Managed Grafana service #4495

Merged
merged 36 commits into from
Mar 22, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
0d142ab
POC: update with command list to implement
yugangw-msft Jan 7, 2022
86a327c
consolidate control plane and dashboard commands
yugangw-msft Jan 8, 2022
f6f0398
half done: make resource group and location optional
yugangw-msft Jan 9, 2022
9f1d64a
more commands and code style fixes
yugangw-msft Jan 15, 2022
118d7c1
add a validator to workspace https://bugs.python.org/issue9334
yugangw-msft Jan 16, 2022
efb83c6
catch up a few TODOs
yugangw-msft Jan 16, 2022
a0e4c6c
Consolidate data source CRUD commands with bug fixes and samples
yugangw-msft Jan 17, 2022
00776b1
add help
yugangw-msft Jan 17, 2022
bff5d4e
bug fixes on data source commands
yugangw-msft Jan 19, 2022
8ea48c2
fix style errors
yugangw-msft Jan 22, 2022
055a128
fix bugs in data-source commands
yugangw-msft Jan 23, 2022
542bc1a
clean up command help
yugangw-msft Jan 23, 2022
fc77db4
upload whl file for tempoaray measures, before we publish it officially
yugangw-msft Jan 24, 2022
5b896a9
update readme and bug fixes
yugangw-msft Jan 24, 2022
ef61108
update get_start doc
yugangw-msft Jan 27, 2022
3cfa442
Update get_start,md
yugangw-msft Jan 25, 2022
22ecfcd
fix typos in get_start.md
yugangw-msft Jan 27, 2022
fa4679e
add vendor SDK
yugangw-msft Mar 5, 2022
3ccce00
new extension
yugangw-msft Mar 5, 2022
7e605bb
new extension built from azdev
yugangw-msft Mar 5, 2022
a439200
rename from ags to amg
yugangw-msft Mar 6, 2022
c2eda5d
add tag support
yugangw-msft Mar 6, 2022
4c97ebd
Update readme.rst
yugangw-msft Mar 6, 2022
4586d2d
undo non related changes
yugangw-msft Mar 6, 2022
f168b98
rename readme to markdown
yugangw-msft Mar 6, 2022
73a4e15
address lint error
yugangw-msft Mar 8, 2022
8b58a18
address linter error
yugangw-msft Mar 8, 2022
db45158
more fix towards command lint error
yugangw-msft Mar 8, 2022
e4d5a62
register the command module in a few common file
yugangw-msft Mar 8, 2022
5c501d8
add import
yugangw-msft Mar 11, 2022
cf46859
support gallery import
yugangw-msft Mar 12, 2022
4242517
use deep copy
yugangw-msft Mar 12, 2022
1a30dc7
address review feedback
yugangw-msft Mar 13, 2022
aa7b87e
set the minimum cli core version
yugangw-msft Mar 18, 2022
c5f3832
fix a bug in 'az grafana user show'
yugangw-msft Mar 19, 2022
8f1c5a1
Remove the 'id' on creating dashboard to prevent 'Not Found' error
yugangw-msft Mar 22, 2022
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
Prev Previous commit
Next Next commit
consolidate control plane and dashboard commands
  • Loading branch information
yugangw-msft committed Mar 8, 2022
commit 86a327c1ec6b2df43598e7af81421ebd2e7f2fe1
2 changes: 1 addition & 1 deletion src/ags/azext_ags/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def __init__(self, cli_ctx=None):
operations_tmpl='azext_ags.custom#{}',
client_factory=cf_ags)
super(AgsCommandsLoader, self).__init__(cli_ctx=cli_ctx,
custom_command_type=ags_custom)
custom_command_type=ags_custom)

def load_command_table(self, args):
from azext_ags.commands import load_command_table
Expand Down
38 changes: 34 additions & 4 deletions src/ags/azext_ags/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@
short-summary: Show details of a Azure Managed Dashboard for Grafana.
"""

# helps['grafana update'] = """
# type: command
# short-summary: Update a Azure Managed Dashboard for Grafana.
# """
helps['grafana update'] = """
type: command
short-summary: Update a Azure Managed Dashboard for Grafana.
"""

helps['grafana show'] = """
type: command
Expand Down Expand Up @@ -65,19 +65,49 @@
helps['grafana dashboard create'] = """
type: group
short-summary: Create a new dashboard.
examples:
- name: Create a dashboard with definition in a json file. For quick start, clone from the output of "az grafana dashboard show", remove "id" and "uid", and apply changes.
text: |
az grafana dashboard create -g MyResourceGroup -n MyGrafana --dashboard-definition @c:\temp\dashboard.json
"""

helps['grafana dashboard update'] = """
type: group
short-summary: Update a new dashboard.
examples:
- name: Update a dashboard with definition in a json file. For quick start, get existing configuration from "az grafana dashboard show", and apply changes.
"version" field need to be updated, and "overwrite" field should be true.
text: |
az grafana dashboard update -g MyResourceGroup -n MyGrafana --dashboard-definition @c:\temp\dashboard.json
"""

helps['grafana dashboard list'] = """
type: group
short-summary: List all dashboards of an instance.
examples:
- name: Find the dashboard for K8s API Server and retrieve the unique identifier(in order to invoke "az grafana dashboard show" command)
text: |
az grafana dashboard list -g MyResourceGroup -n MyGrafana --query "[?contains(@.title, 'API server')].uid"
"""

helps['grafana dashboard show'] = """
type: group
short-summary: show the detail of a dashboard.
examples:
- name: Get details of a dashboard specified by an unique identifier(use "az grafana dashboard list" command to retrieve the uid)
text: |
az grafana dashboard show -g MyResourceGroup -n MyGrafana --uid VdrOA7jGz
- name: Get home dashboard
text: |
az grafana dashboard show -g MyResourceGroup -n MyGrafana --show-home-dashboard
"""

helps['grafana dashboard delete'] = """
type: group
short-summary: delete a dashboard
examples:
- name: Delete a dashboard specified by an unique identifier(use "az grafana dashboard list" command to retrieve the uid)
text: |
az grafana dashboard delete -g MyResourceGroup -n MyGrafana --uid VdrOA7jGz

"""
27 changes: 21 additions & 6 deletions src/ags/azext_ags/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,30 @@

def load_arguments(self, _):

from azure.cli.core.commands.parameters import tags_type
from azure.cli.core.commands.parameters import tags_type, get_three_state_flag
from azure.cli.core.commands.validators import get_default_location_from_resource_group

ags_name_type = CLIArgumentType(options_list='--ags-name-name', help='Name of the Azure Managed Dashboard for Grafana.', id_part='name')
grafana_name_type = CLIArgumentType(options_list='--grafana-name',
help='Name of the Azure Managed Dashboard for Grafana.',
id_part='name')

with self.argument_context('ags') as c:
with self.argument_context('grafana') as c:
c.argument('tags', tags_type)
c.argument('location', validator=get_default_location_from_resource_group)
c.argument('ags_name', ags_name_type, options_list=['--name', '-n'])
c.argument('grafana_name', grafana_name_type, options_list=['--name', '-n'])
c.argument('uid', options_list=['--unique-identifier', '--uid'],
help=("The unique identifier (uid) of a dashboard can be used for uniquely identifying a dashboard or data source "
"between multiple Grafana installs. It’s automatically generated if not provided on creating. "
"The uid allows having consistent URLs for accessing dashboards or data sources when syncing "
"between multiple Grafana installs"))
c.argument('id', help=("The identifier (id) of a dashboard/data source is an auto-incrementing "
"numeric value and is only unique per Grafana install."))

with self.argument_context('ags list') as c:
c.argument('ags_name', ags_name_type, id_part=None)
with self.argument_context('grafana create') as c:
c.argument('enable_system_assigned_identity', arg_type=get_three_state_flag())

with self.argument_context('grafana dashboard') as c:
c.argument('dashboard_definition', help="The complete dashboard model in json string, or a path to a file with such json string")

with self.argument_context('grafana dashboard show') as c:
c.argument('show_home_dashboard', arg_type=get_three_state_flag())
82 changes: 41 additions & 41 deletions src/ags/azext_ags/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,53 +18,53 @@ def load_command_table(self, _):
# TODO: ensure HTTP doc url in the help

with self.command_group('grafana', is_preview=True) as g:
g.custom_command('create', 'create_ags')
g.custom_command('delete', 'delete_ags')
g.custom_command('list', 'list_ags')
g.custom_command('show', 'show_ags')
g.custom_command('get-short-url', 'get_short_url') # TODO
g.generic_update_command('update', setter_name='update', custom_func_name='update_ags') # TODO
g.custom_command('create', 'create_grafana')
g.custom_command('delete', 'delete_grafana')
g.custom_command('list', 'list_grafana')
g.custom_command('show', 'show_grafana')
# g.custom_command('update', 'update_grafana')
g.custom_command('get-short-url', 'get_short_url') # TODO

with self.command_group('grafana dashboard') as g:
g.custom_command('create', 'create_dashboard') # TODO need examples
g.custom_command('update', 'update_dashboard') # TODO
g.custom_command('create', 'create_dashboard') # TODO need examples
g.custom_command('delete', 'delete_dashboard')
g.custom_command('list', 'list_dashboards')
g.custom_command('show', 'show_dashboard') # TODO handle HOME dashboard
g.custom_command('get-tags', 'get_dashboard_tags') # TODO handle HOME dashboard
g.custom_command('show', 'show_dashboard') # TODO handle HOME dashboard
g.custom_command('update', 'update_dashboard') # TODO
# g.custom_command('get-tags', 'get_dashboard_tags') # TODO handle HOME dashboard

with self.command_group('grafana data-source') as g:
g.custom_command('create', 'create_data_source') # TODO
g.custom_command('list', 'list_data_sources')
g.custom_command('show', 'show_data_source') #TODO handle both id, uid, name
g.custom_command('delete', 'delete_data_source') #TODO handle both id, uid, name
g.custom_command('query', 'query_data_source') #TODO handle both id, uid, name
#with self.command_group('grafana data-source') as g:
# g.custom_command('create', 'create_data_source') # TODO
# g.custom_command('list', 'list_data_sources')
# g.custom_command('show', 'show_data_source') # TODO handle both id, uid, name
# g.custom_command('delete', 'delete_data_source') # TODO handle both id, uid, name
# g.custom_command('query', 'query_data_source') # TODO handle both id, uid, name
# g.custom_command('test', 'test_data_source') # TODO

with self.command_group('grafana user'):
g.custom_command('list', 'list_users') # TODO
g.custom_command('show', 'show_user') # TODO
g.custom_command('actual-user', 'get_actual_user') # TODO
g.custom_command('star-dashboard', 'star_dashboard') # TODO consider to move under "dashboard"
g.custom_command('unstar-dashboard', 'unstar_dashboard') # TODO consider to move under "dashboard"
#with self.command_group('grafana user'):
# g.custom_command('list', 'list_users') # TODO
# g.custom_command('show', 'show_user') # TODO
# g.custom_command('actual-user', 'get_actual_user') # TODO
# g.custom_command('star-dashboard', 'star_dashboard') # TODO consider to move under "dashboard"
# g.custom_command('unstar-dashboard', 'unstar_dashboard') # TODO consider to move under "dashboard"

with self.command_group('alert'):
g.custom_command('list', 'list_alerts')
g.custom_command('show', 'show-alert')
g.custom_command('pause', 'pause_alert') # TODO handle "pause-all"
#with self.command_group('grafana alert'):
# g.custom_command('list', 'list_alerts')
# g.custom_command('show', 'show-alert')
# g.custom_command('pause', 'pause_alert') # TODO handle "pause-all"

with self.command_group('alert notification-channel'):
g.custom_command('create', 'create_notification_channel') # TODO
g.custom_command('list', 'list_notification_channels') # TODO handle look up modes
g.custom_command('show', 'show_notification_channel') # TODO handle both uid and id
g.custom_command('delete', 'delete_notification_channel') # TODO handle both uid and id
g.custom_command('update', 'update_notification_channel') # TODO handle both uid and id
g.custom_command('test', 'test_notification_channel') # TODO handle both uid and id

with self.command_group('annotation'):
g.custom_command('create', 'create_annotation') # TODO
g.custom_command('create', 'list_annotation') # TODO (find)
g.custom_command('update', 'update_annotation') # TODO
g.custom_command('patch', 'patch_annotation') # TODO
g.custom_command('delete', 'delete_annotation') # TODO
g.custom_command('find-tags', 'find_annotation_tags') # TODO
#with self.command_group('grafana alert notification-channel'):
# g.custom_command('create', 'create_notification_channel') # TODO
# g.custom_command('list', 'list_notification_channels') # TODO handle look up modes
# g.custom_command('show', 'show_notification_channel') # TODO handle both uid and id
# g.custom_command('delete', 'delete_notification_channel') # TODO handle both uid and id
# g.custom_command('update', 'update_notification_channel') # TODO handle both uid and id
# g.custom_command('test', 'test_notification_channel') # TODO handle both uid and id

#with self.command_group('grafana annotation'):
# g.custom_command('create', 'create_annotation') # TODO
# g.custom_command('create', 'list_annotation') # TODO (find)
# g.custom_command('update', 'update_annotation') # TODO
# g.custom_command('patch', 'patch_annotation') # TODO
# g.custom_command('delete', 'delete_annotation') # TODO
# g.custom_command('find-tags', 'find_annotation_tags') # TODO
133 changes: 76 additions & 57 deletions src/ags/azext_ags/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,22 @@
from azure.cli.core.util import should_disable_connection_verify


def create_ags(cmd, resource_group_name, ags_name, location=None, tags=None):
def create_grafana(cmd, resource_group_name, grafana_name,
enable_system_assigned_identity=True, location=None):
client = get_mgmt_service_client(cmd.cli_ctx, ResourceType.MGMT_RESOURCE_RESOURCES)
r = '{"sku": {"name": "Basic"}, "location": "{0}"}'.format(location)
return client.resources.begin_create_or_update(self.resource_group_name, "Microsoft.Dashboard", "",
"grafana", ags_name, "2021-09-01-preview", json.loads(r))
# r = '{{"sku": {{"name": "Basic"}}, "location": "{0}"}}'.format(location)
resource = {
"sku": {
"name": "standard"
},
"location": location,
"identity": {"type": "SystemAssigned"} if enable_system_assigned_identity else None
}
return client.resources.begin_create_or_update(resource_group_name, "Microsoft.Dashboard", "",
"grafana", grafana_name, "2021-09-01-preview", resource)


def list_ags(cmd, resource_group_name=None):
def list_grafana(cmd, resource_group_name=None):
filters = []
if resource_group_name:
filters.append("resourceGroup eq '{}'".format(resource_group_name))
Expand All @@ -34,93 +42,104 @@ def list_ags(cmd, resource_group_name=None):
return list(resources)


def show_ags(cmd, resource_group_name, ags_name):
def show_grafana(cmd, resource_group_name, grafana_name):
client = get_mgmt_service_client(cmd.cli_ctx, ResourceType.MGMT_RESOURCE_RESOURCES)
return client.resources.get(resource_group_name, "Microsoft.Dashboard", "", "grafana", ags_name, "2021-09-01-preview")

return client.resources.get(resource_group_name, "Microsoft.Dashboard",
"", "grafana", grafana_name, "2021-09-01-preview")

def delete_ags(cmd, resource_group_name, ags_name):

def delete_grafana(cmd, resource_group_name, grafana_name):
client = get_mgmt_service_client(cmd.cli_ctx, ResourceType.MGMT_RESOURCE_RESOURCES)
return client.resources.begin_delete(resource_group_name, "Microsoft.Dashboard", "", "grafana", ags_name, "2021-09-01-preview")
return client.resources.begin_delete(resource_group_name, "Microsoft.Dashboard",
"", "grafana", grafana_name, "2021-09-01-preview")


def show_dashboard(cmd, resource_group_name, ags_name, uid=None):
def show_dashboard(cmd, resource_group_name, grafana_name, uid=None, show_home_dashboard=None):
if uid:
path = "/api/dashboards/uid/" + uid
elif show_home_dashboard:
path = "/api/dashboards/home"
else:
path = "/api/search"
response = _send_request(cmd, resource_group_name, ags_name, path)

response = _send_request(cmd, resource_group_name, grafana_name, "get", path)
return json.loads(response.content)


def list_dashboards(cmd, resource_group_name, ags_name):
return show_dashboard(cmd, resource_group_name, ags_name, None)
def list_dashboards(cmd, resource_group_name, grafana_name):
return show_dashboard(cmd, resource_group_name, grafana_name, None)


def create_dashboard(cmd, resource_group_name, ags_name, dashboard_defition):
response = _send_request(cmd, resource_group_name, ags_name, "/api/dashboards/db", dashboard_defition)
def create_dashboard(cmd, resource_group_name, grafana_name, dashboard_definition):
import os
potentail_file_path = os.path.expanduser(dashboard_definition)
if os.path.exists(potentail_file_path):
from azure.cli.core.util import read_file_content
dashboard_definition = read_file_content(potentail_file_path)
response = _send_request(cmd, resource_group_name, grafana_name, "post", "/api/dashboards/db", dashboard_definition)
return json.loads(response.content)


def delete_dashboard(cmd, resource_group_name, ags_name, uid):
requests.delete(endpoint + path, verify=(not should_disable_connection_verify()))
def update_dashboard(cmd, resource_group_name, grafana_name, dashboard_definition):
return create_dashboard(cmd, resource_group_name, grafana_name, dashboard_definition)


def list_data_sources(cmd, resource_group_name, ags_name):
response = _send_request(cmd, resource_group_name, ags_name, "/api/datasources")
return json.loads(response.content)
def delete_dashboard(cmd, resource_group_name, grafana_name, uid):
_send_request(cmd, resource_group_name, grafana_name, "delete", "/api/dashboards/uid/" + uid)


def create_data_source(cmd, resource_group_name, ags_name, data_source_type, data_source_subscription=None, data_source_name=None):
path = "/api/datasources"
def list_data_sources(cmd, resource_group_name, grafana_name):
response = _send_request(cmd, resource_group_name, grafana_name, "get", "/api/datasources")
return json.loads(response.content)

# TODO make it easier

data_source = {
"access": "proxy",
"basicAuth": False,
# "database": "",
#"id": 1,
#"isDefault": false,
"jsonData": {
"azureAuthType": "msi",
"subscriptionId": data_source_subscription or subscription
},
"name": data_source_name or data_source_type, # "Azure Monitor",
# "orgId": 1,
# "password": "",
# "readOnly": false,
"type": data_source_type, # "grafana-azure-monitor-datasource",
# "typeLogoUrl": "public/app/plugins/datasource/grafana-azure-monitor-datasource/img/logo.jpg",
# "typeName": "Azure Monitor",
# "uid": "azure-monitor-oob",
# "url": "",
# "user": ""
}
def create_data_source(cmd, resource_group_name, grafana_name, data_source_type, data_source_subscription=None, data_source_name=None):
# TODO make it easier

response = _send_request(cmd, resource_group_name, ags_name, "/api/datasources", data_source)
#data_source = {
# "access": "proxy",
# "basicAuth": False,
# # "database": "",
# # "id": 1,
# # "isDefault": false,
# "jsonData": {
# "azureAuthType": "msi",
# "subscriptionId": data_source_subscription or get_subscription_id(cmd.cli_ctx)
# },
# "name": data_source_name or data_source_type, # "Azure Monitor",
# # "orgId": 1,
# # "password": "",
# # "readOnly": false,
# "type": data_source_type, # "grafana-azure-monitor-datasource",
# # "typeLogoUrl": "public/app/plugins/datasource/grafana-azure-monitor-datasource/img/logo.jpg",
# # "typeName": "Azure Monitor",
# # "uid": "azure-monitor-oob",
# # "url": "",
# # "user": ""
#}

response = _send_request(cmd, resource_group_name, grafana_name, "post", "/api/datasources", data_source)
return json.loads(response.content)


def _send_request(cmd, resource_group_name, ags_name, path, body=None):
grafana = show_ags(cmd, resource_group_name, ags_name)
def _send_request(cmd, resource_group_name, grafana_name, http_method, path, body=None):
grafana = show_grafana(cmd, resource_group_name, grafana_name)
endpoint = grafana.properties["endpoint"]

from azure.cli.core._profile import Profile
profile = Profile(cli_ctx=cmd.cli_ctx)
# this might be a cross tenant scenario, so pass subscription to get_raw_token
subscription = get_subscription_id(cmd.cli_ctx)
creds, _, _ = profile.get_raw_token(subscription=subscription, resource="ce34e7e5-485f-4d76-964f-b3d2b16d1e4f") # TODO, support dogfood
creds, _, _ = profile.get_raw_token(subscription=subscription,
resource="ce34e7e5-485f-4d76-964f-b3d2b16d1e4f") # TODO, support dogfood

headers = {
'Content-Type': 'application/json',
'authorization': 'Bearer ' + creds[1]
}

return requests.post(endpoint + path, headers=headers, json=body,
verify=(not should_disable_connection_verify()))





return requests.request(http_method,
url=endpoint + path,
headers=headers,
data=body,
verify=(not should_disable_connection_verify()))
2 changes: 1 addition & 1 deletion src/ags/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,4 @@
packages=find_packages(),
install_requires=DEPENDENCIES,
package_data={'azext_ags': ['azext_metadata.json']},
)
)