mgr/cephadm: Add GrafanaSpec.initial_admin_password

By default, we're not creating any admin accout for Grafana now,
but we're adding an option to set the grafana password manually using:

```yaml
service_type: grafana
spec:
  initial_admin_password: mypassword
```

Users can then easily log into Grafana with the given password.

Fixes: https://tracker.ceph.com/issues/48291
Signed-off-by: Sebastian Wagner <sewagner@redhat.com>
This commit is contained in:
Sebastian Wagner 2021-07-16 16:20:32 +02:00
parent f2b0b45176
commit fdae665a2f
No known key found for this signature in database
GPG Key ID: 8D2442807E6979F8
6 changed files with 112 additions and 9 deletions

View File

@ -161,6 +161,8 @@ For example, if you had changed the prometheus image
ceph config rm mgr mgr/cephadm/container_image_prometheus
.. _cephadm-overwrite-jinja2-templates:
Using custom configuration files
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -180,7 +182,7 @@ preserved and automatically applied on future deployments of these services.
The configuration of the custom template is also preserved when the default
configuration of cephadm changes. If the updated configuration is to be used,
the custom template needs to be migrated *manually*.
the custom template needs to be migrated *manually* after each upgrade of Ceph.
Option names
""""""""""""
@ -338,6 +340,30 @@ update its configuration:
The ``reconfig`` command also sets the proper URL for Ceph Dashboard.
Setting the initial admin password
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
By default, Grafana will not create an initial
admin user. In order to create the admin user, please create a file
``grafana.yaml`` with this content:
.. code-block:: yaml
service_type: grafana
spec:
initial_admin_password: mypassword
Then apply this specification:
.. code-block:: bash
ceph orch apply -i grafana.yaml
ceph orch redeploy grafana
Grafana will now create an admin user called ``admin`` with the
given password.
Setting up Alertmanager
-----------------------

View File

@ -7,7 +7,7 @@ from urllib.parse import urlparse
from mgr_module import HandleCommandResult
from orchestrator import DaemonDescription
from ceph.deployment.service_spec import AlertManagerSpec, ServiceSpec
from ceph.deployment.service_spec import AlertManagerSpec, GrafanaSpec, ServiceSpec
from cephadm.services.cephadmservice import CephadmService, CephadmDaemonDeploySpec
from cephadm.services.ingress import IngressSpec
from mgr_util import verify_tls, ServerConfigException, create_self_signed_cert, build_url
@ -57,8 +57,11 @@ class GrafanaService(CephadmService):
'value': 'false',
})
spec: GrafanaSpec = cast(
GrafanaSpec, self.mgr.spec_store.active_specs[daemon_spec.service_name])
grafana_ini = self.mgr.template.render(
'services/grafana/grafana.ini.j2', {
'initial_admin_password': spec.initial_admin_password,
'http_port': daemon_spec.ports[0] if daemon_spec.ports else self.DEFAULT_SERVICE_PORT,
'http_addr': daemon_spec.ip if daemon_spec.ip else ''
})

View File

@ -13,6 +13,10 @@
http_port = {{ http_port }}
http_addr = {{ http_addr }}
[security]
{% if not initial_admin_password %}
disable_initial_admin_creation = true
{% else %}
admin_user = admin
admin_password = admin
admin_password = {{ initial_admin_password }}
{% endif %}
allow_embedding = true

View File

@ -4,7 +4,7 @@ import yaml
import pytest
from unittest.mock import MagicMock, call, patch
from unittest.mock import MagicMock, call, patch, ANY
from cephadm.serve import CephadmServe
from cephadm.services.cephadmservice import MonService, MgrService, MdsService, RgwService, \
@ -16,7 +16,7 @@ from cephadm.services.monitoring import GrafanaService, AlertmanagerService, Pro
NodeExporterService
from cephadm.module import CephadmOrchestrator
from ceph.deployment.service_spec import IscsiServiceSpec, MonitoringSpec, AlertManagerSpec, \
ServiceSpec, RGWSpec
ServiceSpec, RGWSpec, GrafanaSpec
from cephadm.tests.fixtures import with_host, with_service, _run_cephadm, async_side_effect
from orchestrator import OrchestratorError
@ -332,7 +332,7 @@ class TestMonitoring:
cephadm_module.set_store('grafana_crt', 'c')
cephadm_module.set_store('grafana_key', 'k')
with with_service(cephadm_module, MonitoringSpec('prometheus')) as _, \
with_service(cephadm_module, MonitoringSpec('grafana')) as _:
with_service(cephadm_module, GrafanaSpec('grafana')) as _:
files = {
'grafana.ini': dedent("""
# This file is generated by cephadm.
@ -350,8 +350,7 @@ class TestMonitoring:
http_port = 3000
http_addr =
[security]
admin_user = admin
admin_password = admin
disable_initial_admin_creation = true
allow_embedding = true""").lstrip(), # noqa: W291
'provisioning/datasources/ceph-dashboard.yml': dedent("""
# This file is generated by cephadm.
@ -389,6 +388,47 @@ class TestMonitoring:
stdin=json.dumps({"files": files}),
image='')
@patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('{}'))
def test_grafana_initial_admin_pw(self, cephadm_module: CephadmOrchestrator):
with with_host(cephadm_module, 'test'):
with with_service(cephadm_module, GrafanaSpec(initial_admin_password='secure')):
out = cephadm_module.cephadm_services['grafana'].generate_config(
CephadmDaemonDeploySpec('test', 'daemon', 'grafana'))
assert out == (
{
'files':
{
'certs/cert_file': ANY,
'certs/cert_key': ANY,
'grafana.ini':
'# This file is generated by cephadm.\n'
'[users]\n'
' default_theme = light\n'
'[auth.anonymous]\n'
' enabled = true\n'
" org_name = 'Main Org.'\n"
" org_role = 'Viewer'\n"
'[server]\n'
" domain = 'bootstrap.storage.lab'\n"
' protocol = https\n'
' cert_file = /etc/grafana/certs/cert_file\n'
' cert_key = /etc/grafana/certs/cert_key\n'
' http_port = 3000\n'
' http_addr = \n'
'[security]\n'
' admin_user = admin\n'
' admin_password = secure\n'
' allow_embedding = true',
'provisioning/datasources/ceph-dashboard.yml':
'# This file is generated by cephadm.\n'
'deleteDatasources:\n'
'\n'
'datasources:\n'
}
},
[],
)
@patch("cephadm.serve.CephadmServe._run_cephadm")
def test_monitoring_ports(self, _run_cephadm, cephadm_module: CephadmOrchestrator):
_run_cephadm.side_effect = async_side_effect(('{}', '', 0))

View File

@ -432,7 +432,7 @@ class ServiceSpec(object):
'alertmanager': AlertManagerSpec,
'ingress': IngressSpec,
'container': CustomContainerSpec,
'grafana': MonitoringSpec,
'grafana': GrafanaSpec,
'node-exporter': MonitoringSpec,
'prometheus': MonitoringSpec,
}.get(service_type, cls)
@ -1093,3 +1093,27 @@ class AlertManagerSpec(MonitoringSpec):
yaml.add_representer(AlertManagerSpec, ServiceSpec.yaml_representer)
class GrafanaSpec(MonitoringSpec):
def __init__(self,
service_type: str = 'grafana',
service_id: Optional[str] = None,
placement: Optional[PlacementSpec] = None,
unmanaged: bool = False,
preview_only: bool = False,
config: Optional[Dict[str, str]] = None,
networks: Optional[List[str]] = None,
port: Optional[int] = None,
initial_admin_password: Optional[str] = None
):
assert service_type == 'grafana'
super(GrafanaSpec, self).__init__(
'grafana', service_id=service_id,
placement=placement, unmanaged=unmanaged,
preview_only=preview_only, config=config, networks=networks, port=port)
self.initial_admin_password = initial_admin_password
yaml.add_representer(GrafanaSpec, ServiceSpec.yaml_representer)

View File

@ -249,6 +249,12 @@ service_name: grafana
spec:
port: 1234
---
service_type: grafana
service_name: grafana
spec:
initial_admin_password: secure
port: 1234
---
service_type: ingress
service_id: rgw.foo
service_name: ingress.rgw.foo