Esempio n. 1
0
def renameKioskOrg(user, password):
    """Rename the default organization (id=1) to what is in ``admins/_kiosk.yaml``.
    
    Parameters
    ==========
    user : `str`
        ``login`` of the Grafana account that is making the API request.
    password : `str`
        ``password`` of the Grafana account that is making the API request.
    
    Raises
    ======
    FileNotFoundError
        Raised if ``admins/_kiosk.yaml`` can't be found by this script. The file
        must contain the name of the default organization and a list of users which
        will be registered to it.
    ValueError
        Raised if 0 or more than 1 organizations exist in ``admins/_kiosk.yaml``.
        The number of organizations in this file must be exactly 1.
    grafanaAPI.APIError
        Raised if the request replies with a status code in the 4XX or 5XX range.
        The causes include: The first organization does not exist, invalid 
        credentials (`user` and `password`), the user doesn't have permission to
        make this request or the server is not responding. Check the error messages
        for more information.
    
    See Also
    ========
    grafanaAPI.request
    
    Notes
    =====
    Affects the current context organization for the user.
    """
    try:
        kiosk = yutil.getYamlContent('{}/_kiosk.yaml'.format(adminsDir))
    except FileNotFoundError as exc:
        print(
            'Error: The file "{}/_kiosk.yaml" is missing. Make sure that the directory is set correctly'
            ' in "config.yaml" and that the file has the correct name. It must contain information about '
            'the account(s) that are provisioned for the first organization. You can change the name of '
            'the org in the key of the hash, but the filename shouldn\'t be changed.'
            .format(adminsDir))
        raise exc

    if len(kiosk) != 1:
        raise ValueError(
            '{}/_kiosk.yaml must contain 1 organization.'.format(adminsDir))
    orgName = next(iter(kiosk))

    data = {'name': orgName}
    try:
        r = gapi.request('put', 'orgs/1', user, password, data)
    except gapi.APIError as exc:
        print(
            'Error: There was an error when trying to change the default organization\'s name. Check '
            'the requests\' output below to see Grafana\'s response. There might be a problem with '
            ' Grafana, the API\'s account might be misconfigured or organization 1 might not exist.'
        )
        raise exc
def loadProvisionedUsers(accountsDir, user, password):
    """Load users from YAML config, create them if the don't exist, get their IDs.
    
    Parameters
    ==========
    accountsDir : `str`
        Path to the directory where the accounts YAML files (symlinks) are stored.
    user : `str`
        ``login`` of the Grafana account that is making the API request.
    password : `str`
        ``password`` of the Grafana account that is making the API request.
    
    Returns
    =======
    existingUsers : `dict`
        Dictionary containing all the Grafana users in the provisioning 
        configuration. Consists of one key per user, where the key is the user's
        username and the value is the user's ID in Grafana.
    
    Raises
    ======
    ValueError
        Raised if there is more than one user with the same username in the YAML
        configuration files. This could happen if two orgs are trying to provision
        the same user, in this case only one of them should provision the user in
        the ``accounts.yaml`` file, but both of them should have the user in their
        ``org.yaml`` file.
    grafanaAPI.APIError
        Raised if the request replies with a status code in the 4XX or 5XX range.
        The causes include: email already in use, invalid password or email,
        invalid credentials (`user` and `password`), the user doesn't have
        permission to make this request or the server is not responding. Check the
        error messages for more information.
    yaml.YAMLError
        Raised if an accounts file does not contain a valid YAML format.
    PermissionError:
        Raised if the script does not have read permissions on an accounts file.
    
    See Also
    ========
    getOrCreateUser
    yamlUtility.getYamlContent
    """
    existingUsers = {}
    # Don't open the files that start with '_'
    # https://stackoverflow.com/a/36295481
    for accountsFile in glob.glob('{}/[!_]*.yaml'.format(accountsDir)):
        accountsList = yutil.getYamlContent(accountsFile)

        if accountsList is not None:
            for account in accountsList:
                login = account['login']
                if login in existingUsers:
                    raise ValueError(
                        'Duplicate user {} in the yaml configuration. {}'.
                        format(login, accountsFile))
                existingUsers[login] = getOrCreateUser(account, user, password)
    return existingUsers
def loadProvisionedOrgs(orgsDir):
    """Load orgs from YAML config into dict indexed by name, storing user lists.
    
    Parameters
    ==========
    orgsDir : `str`
        Path to the directory where the orgs YAML files (symlinks) are stored.
    
    Returns
    =======
    provOrgs : `dict`
        Dictionary containing all orgs in the provisioning configuration. Consists
        of one key per org, where the key is the org's name and the value is a list
        with the accounts to be provisioned for the organization.
    
    Raises
    ======
    ValueError
        Raised if there is more than one organization with the same name in the
        YAML configuration files or if there isn't exactly one org in a given
        ``org.yaml`` file.
    yaml.YAMLError
        Raised if an org file does not contain a valid YAML format.
    PermissionError:
        Raised if the script does not have read permissions on an org file.
    
    See Also
    ========
    yamlUtility.getYamlContent
    """
    provOrgs = {}
    for orgFile in glob.glob('{}/[!_]*.yaml'.format(orgsDir)):
        orgDict = yutil.getYamlContent(orgFile)
        numOrgs = len(orgDict)
        if numOrgs != 1:
            raise ValueError(
                'There must be 1 org in the configuration file and {} were found. {}'
                .format(numOrgs, orgFile))
        orgName = next(iter(orgDict))
        if orgName in provOrgs:
            raise ValueError(
                'Duplicate organization {} in the yaml configuration. {}'.
                format(orgName, orgFile))
        # Save list of org's accounts
        provOrgs[orgName] = orgDict[orgName]
    return provOrgs
    # Grafana, because orgs are created when processing new input
    gadmin = yutil.getSuperAdminLogin()
    for orgName in provOrgs:
        orgId = gapi.createOrg(orgName, gadmin, user, password)
        reviewOrgUsers(orgId, provOrgs[orgName], existingUsers, user, password)


if __name__ == '__main__':
    gapi.timeout = yutil.config['timeout']
    provisioningDir = yutil.config['provisioningDir']
    adminsDir = '{}/admins'.format(provisioningDir)
    accountsDir = '{}/accounts'.format(provisioningDir)
    orgsDir = '{}/orgs'.format(provisioningDir)

    # Get API user and password
    api = yutil.getYamlContent('{}/_superAdmins.yaml'.format(adminsDir))['api']
    admLogin = api['login']
    admPasswd = api['password']

    provOrgs = loadProvisionedOrgs(orgsDir)
    existingUsers = loadProvisionedUsers(accountsDir, admLogin, admPasswd)

    # Get all the orgs in Grafana
    r = gapi.request('get', 'orgs', admLogin, admPasswd)
    grafOrgs = r.json()

    reviewExistingOrgs(grafOrgs, provOrgs, existingUsers, admLogin, admPasswd)
    createAndReviewOrgs(provOrgs, existingUsers, admLogin, admPasswd)

    # Review users for the first organization (Kiosk)
    kiosk = yutil.getYamlContent('{}/_kiosk.yaml'.format(adminsDir))
Esempio n. 5
0
            'Error: There was an error when trying to change the default organization\'s name. Check '
            'the requests\' output below to see Grafana\'s response. There might be a problem with '
            ' Grafana, the API\'s account might be misconfigured or organization 1 might not exist.'
        )
        raise exc


_path = os.path.abspath(os.path.dirname(__file__))
_lastInitialization = '{}/lastInitialization.txt'.format(_path)
if __name__ == '__main__' and (not os.path.exists(_lastInitialization)
                               or os.path.getsize(_lastInitialization) == 0):
    gapi.timeout = yutil.config['timeout']
    adminsDir = '{}/admins'.format(yutil.config['provisioningDir'])

    try:
        supers = yutil.getYamlContent('{}/_superAdmins.yaml'.format(adminsDir))
    except FileNotFoundError as exc:
        print(
            'The file "{}/_superAdmins.yaml" is missing! Make sure that the directory is set correctly in '
            '"config.yaml" and that the file has the correct name. It must contain information for the two '
            'Grafana Admin accounts, the main one (id=1) and the API account which the provisioning script '
            'should use. See the structure in the documentation and default files.'
            .format(adminsDir))
        raise exc

    grafAdmin = supers['grafanaAdmin']
    admLogin = '******'
    admPasswd = 'admin'

    changeAdminPassword(grafAdmin['password'], admLogin, admPasswd)
    admPasswd = grafAdmin['password']
def provisionOrg(orgInputDir, user, password, provisionedOrgs):
    """Makes sure that the organization in Grafana is provisioned.
    
    If the organization already exists, get the org's id from Grafana. Else, create
    the org in Grafana and the symlink to ``org.yaml`` inside ``orgs/``. Returns
    the org's id and name.
    
    Parameters
    ==========
    orgInputDir : `str`
        The directory where the inputs for this org are stored.
    user : `str`
        ``login`` of the Grafana account that is making the API request.
    password : `str`
        ``password`` of the Grafana account that is making the API request.
    provisionedOrgs : `dict`
        Dictionary containing all orgs in the provisioning configuration. Consists
        of one key per org, where the key is the org's name and the value True for
        all provisioned organizations. We use a dictionary instead of a list
        because it should be faster for searching if an org is provisioned.
    
    Returns
    =======
    orgId : `int`
        ``id`` of the organization inside Grafana.
    orgName: `str`
        Name of the Grafana organization.
    
    Raises
    ======
    ValueError
        Raised if there is more than one organization with the same name in the
        YAML configuration files or if ``org.yaml`` doesn't contain exactly one
        organization. We do not support this feature. Different organizations
        should come separately in different inputs.
    grafanaAPI.APIError
        Raised if the request replies with a status code in the 4XX or 5XX range.
        The causes include: the symlink in ``orgs/`` exists but the organization in
        Grafana doesn't (in this case delete the symlink manually), the org already
        exists in Grafana and the symlink doesn't, invalid credentials (`user` and
        `password`), the user doesn't have permission to make this request or the
        server is not responding. Check the error messages for more information.
    
    See Also
    ========
    grafanaAPI.getOrgId
    grafanaAPI.createOrg
    
    Notes
    =====
    The existance of the symlink should represent the state of the Grafana org: if
    the symlink doesn't exist then the org shouldn't exist either, and if the 
    symlink exists the org should as well. If the creation of the org is
    interrupted halfway through it will need to be deleted manually (or else we
    could be deleting already existing orgs).
    """
    # Get Org name
    file = '{}/org.yaml'.format(orgInputDir)
    orgDict = yutil.getYamlContent(file)
    numOrgs = len(orgDict)
    if numOrgs != 1:
        raise ValueError(
            'There must be 1 org in the configuration file and {} were found. {}'
            .format(numOrgs, file))
    orgName = next(iter(orgDict))
    if orgName in provisionedOrgs:
        raise ValueError(
            'Duplicate organization {} in the yaml configuration. {}/org.yaml'.
            format(orgName, orgInputDir))
    provisionedOrgs[orgName] = True

    # Check if org is provisioned (file or symlink exists in ./orgs)
    symlink = '{}/orgs/{}_org.yaml'.format(provisioningDir, orgName)
    if os.path.exists(symlink):
        # Get org's id
        orgId = gapi.getOrgId(orgName, user, password)
    else:
        # Add the symlink to org
        os.symlink(file, symlink)
        # Create org and get the id
        gadmin = yutil.getSuperAdminLogin()
        try:
            orgId = gapi.createOrg(orgName, gadmin, user, password)
        except Exception as exc:
            os.remove(symlink)
            print(
                'The operation failed while creating or configuring the  organization "{}". If the org was '
                'created, you will need to delete it manually from Grafana. If your org already exists and '
                'doesn\'t need to be created by the provisioning script, you can create a symlink called "{}"'
                'that points to "{}"'.format(orgName, symlink, file))
            raise exc
    return orgId, orgName
def provisionFolders(orgId, orgName, grafanaFolders, orgInputDir,
                     dashboardsDir):
    """Create folder structure in dashboardsDir and configure each folder route.
    
    Uses ``dashboardRoutesTemplate.yaml`` as a template to create the folder
    routes.
    
    Parameters
    ==========
    orgId : `int`
        ID of the org in Grafana to which the dashboards will be provisioned.
    orgName : `str`
        Name of the org in Grafana to which the dashboards will be provisioned.
    grafanaFolders : `list` of `str`
        List containing the names of the folders that are going to be provisioned.
        Just the folder names, not full paths.
    orgInputDir : `str`
        The directory where the inputs for the given org are stored.
    dashboardsDir : `str`
        The directory where Grafana will look for provisioned dashboards.
    
    Raises
    ======
    yaml.YAMLError
        Raised if the template contains an invalid YAML format.
    PermissionError:
        Raised if the script does not have read permissions on the template, or
        if it doesn't have write permissions to the configured ``dashboardsDir`` or
        ``/etc/grafana/provisioning/dashboards/``
    FileNotFoundError:
        Raised if the template doesn't exist or it can't be accessed by the script.
    
    See Also
    ========
    yamlUtility.getYamlContent
    yamlUtility.writeYamlContent
    os.makedirs
    os.symlink
    
    Notes
    =====
    The folder configuration is read by grafana-server when it starts. The
    dashboards in the folders will be checked periodically for updates by Grafana.
    """
    path = os.path.abspath(os.path.dirname(__file__))
    routeYaml = yutil.getYamlContent('{}/dbRoutesTemplate.yaml'.format(path))

    # Create org directory
    orgDashboardsPath = '{}/{}'.format(dashboardsDir, orgName)
    os.makedirs(orgDashboardsPath, exist_ok=True)

    # Delete removed folders and their contents
    existingFolders = getDirList(orgDashboardsPath)
    diff = set(existingFolders) - set(grafanaFolders)
    for oldFolder in diff:
        shutil.rmtree('{}/{}'.format(orgDashboardsPath, oldFolder))

    providerTemplate = routeYaml['providers'][0]
    routeYaml['providers'] = []
    for i in range(len(grafanaFolders)):
        provider = copy.deepcopy(providerTemplate)
        # Route to where the dashboards are going to be stored
        folderPath = '{}/{}'.format(orgDashboardsPath, grafanaFolders[i])
        provider['options']['path'] = folderPath

        provider['updateIntervalSeconds'] = yutil.config[
            'updateIntervalSeconds']
        provider['orgId'] = orgId
        provider['folder'] = grafanaFolders[i]
        provider['name'] = '{}_{}'.format(orgName, grafanaFolders[i])

        routeYaml['providers'].append(provider)

        # Create directory for this Grafana Folder
        if not os.path.isdir(folderPath):
            os.mkdir(folderPath)

    yutil.writeYamlContent(
        '/etc/grafana/provisioning/dashboards/{}_dashboardRoutes.yaml'.format(
            orgName), routeYaml)
        orgId, orgName = provisionOrg(orgInputDir, user, password,
                                      provisionedOrgs)

        # Make symlink for account file
        symlink = '{}/accounts/{}_accounts.yaml'.format(
            provisioningDir, orgName)
        if not os.path.exists(symlink):
            # If the symlink exists but is broken, remove it to add the new one
            if os.path.lexists(symlink):
                os.remove(symlink)
            os.symlink('{}/accounts.yaml'.format(orgInputDir), symlink)

        # Check if there is something new to provision
        stateFile = '{}/.state.yaml'.format(orgInputDir)
        if os.path.exists(stateFile):
            state = yutil.getYamlContent(stateFile)
        else:
            state = {}

        # Datasources
        dSrcFile = '{}/{}'.format(orgInputDir, 'datasources.yaml')
        dSrcYaml = yutil.getYamlContent(dSrcFile)
        modified = False
        now = datetime.now()
        if 'datasourcesDate' in state:
            # time since last modification of file (float)
            lastModified = datetime.utcfromtimestamp(
                os.path.getmtime(dSrcFile))
            lastProvisioned = datetime.strptime(state['datasourcesDate'],
                                                '%Y-%m-%dT%H:%M:%S')
            if lastModified > lastProvisioned: