2020-03-13 08:39:41 +00:00
|
|
|
# Copyright (C) 2020 The Android Open Source Project
|
|
|
|
#
|
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
# you may not use this file except in compliance with the License.
|
|
|
|
# You may obtain a copy of the License at
|
|
|
|
#
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
#
|
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
# See the License for the specific language governing permissions and
|
|
|
|
# limitations under the License.
|
|
|
|
|
|
|
|
import os.path
|
2020-03-05 14:46:34 +00:00
|
|
|
import stat
|
2020-06-19 15:32:19 +00:00
|
|
|
import shutil
|
2020-03-13 08:39:41 +00:00
|
|
|
import subprocess
|
2020-06-19 15:32:19 +00:00
|
|
|
import sys
|
2020-03-05 14:46:34 +00:00
|
|
|
import zipfile
|
|
|
|
|
2020-08-25 11:16:42 +00:00
|
|
|
import _jsonnet
|
2020-03-05 14:46:34 +00:00
|
|
|
import requests
|
2020-03-13 08:39:41 +00:00
|
|
|
import yaml
|
|
|
|
|
|
|
|
from ._globals import HELM_CHARTS
|
|
|
|
|
|
|
|
|
|
|
|
TEMPLATES = [
|
|
|
|
"charts/namespace.yaml",
|
|
|
|
"charts/prometheus",
|
2020-05-13 14:08:04 +00:00
|
|
|
"charts/promtail",
|
2020-03-13 08:39:41 +00:00
|
|
|
"charts/loki",
|
|
|
|
"charts/grafana",
|
|
|
|
"promtail",
|
|
|
|
]
|
|
|
|
|
|
|
|
HELM_REPOS = {
|
2020-09-16 07:57:31 +00:00
|
|
|
"grafana": "https://grafana.github.io/helm-charts",
|
2020-03-13 08:39:41 +00:00
|
|
|
"loki": "https://grafana.github.io/loki/charts",
|
2020-11-24 12:53:45 +00:00
|
|
|
"prometheus-community": "https://prometheus-community.github.io/helm-charts",
|
2020-03-13 08:39:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
LOOSE_RESOURCES = [
|
|
|
|
"namespace.yaml",
|
|
|
|
"configuration",
|
|
|
|
"dashboards",
|
|
|
|
"storage",
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
def _create_dashboard_configmaps(output_dir, namespace):
|
|
|
|
dashboards_dir = os.path.abspath("./dashboards")
|
|
|
|
|
|
|
|
output_dir = os.path.join(output_dir, "dashboards")
|
|
|
|
if not os.path.exists(output_dir):
|
|
|
|
os.mkdir(output_dir)
|
|
|
|
|
2020-08-25 11:16:42 +00:00
|
|
|
for dir_path, _, files in os.walk(dashboards_dir):
|
|
|
|
for dashboard in files:
|
|
|
|
dashboard_path = os.path.join(dir_path, dashboard)
|
|
|
|
dashboard_name, ext = os.path.splitext(dashboard)
|
|
|
|
if ext == ".json":
|
|
|
|
source = f"--from-file={dashboard_path}"
|
|
|
|
elif ext == ".jsonnet":
|
|
|
|
json = _jsonnet.evaluate_file(dashboard_path, ext_codes={"publish": "false"})
|
|
|
|
source = f"--from-literal={dashboard_name}.json='{json}'"
|
|
|
|
else:
|
|
|
|
continue
|
|
|
|
|
|
|
|
output_file = f"{output_dir}/{dashboard_name}.dashboard.yaml"
|
|
|
|
|
|
|
|
command = (
|
|
|
|
f"kubectl create configmap {dashboard_name} -o yaml "
|
|
|
|
f"{source} --dry-run=client --namespace={namespace} "
|
|
|
|
f"> {output_file}"
|
2020-11-20 08:15:55 +00:00
|
|
|
)
|
2020-03-13 08:39:41 +00:00
|
|
|
|
2020-08-25 11:16:42 +00:00
|
|
|
try:
|
|
|
|
subprocess.check_output(command, shell=True)
|
|
|
|
except subprocess.CalledProcessError as err:
|
|
|
|
print(err.output)
|
|
|
|
|
|
|
|
with open(output_file, "r") as f:
|
|
|
|
dashboard_cm = yaml.load(f, Loader=yaml.SafeLoader)
|
|
|
|
dashboard_cm["metadata"]["labels"] = dict()
|
|
|
|
dashboard_cm["metadata"]["labels"]["grafana_dashboard"] = dashboard_name
|
|
|
|
dashboard_cm["data"][f"{dashboard_name}.json"] = dashboard_cm["data"][
|
|
|
|
f"{dashboard_name}.json"
|
|
|
|
].replace('"${DS_PROMETHEUS}"', "null")
|
|
|
|
|
|
|
|
with open(output_file, "w") as f:
|
|
|
|
yaml.dump(dashboard_cm, f)
|
2020-03-13 08:39:41 +00:00
|
|
|
|
|
|
|
|
2020-03-05 15:45:51 +00:00
|
|
|
def _create_promtail_configs(config, output_dir):
|
2020-03-05 14:28:26 +00:00
|
|
|
if not os.path.exists(os.path.join(output_dir, "promtail")):
|
|
|
|
os.mkdir(os.path.join(output_dir, "promtail"))
|
|
|
|
|
2020-05-13 14:08:04 +00:00
|
|
|
with open(os.path.join(output_dir, "promtailLocalConfig.yaml")) as f:
|
2020-03-05 15:45:51 +00:00
|
|
|
for promtail_config in yaml.load_all(f, Loader=yaml.SafeLoader):
|
2020-03-05 14:28:26 +00:00
|
|
|
with open(
|
|
|
|
os.path.join(
|
|
|
|
output_dir,
|
|
|
|
"promtail",
|
|
|
|
"promtail-%s"
|
2020-03-05 15:45:51 +00:00
|
|
|
% promtail_config["scrape_configs"][0]["static_configs"][0][
|
|
|
|
"labels"
|
|
|
|
]["host"],
|
2020-03-05 14:28:26 +00:00
|
|
|
),
|
|
|
|
"w",
|
|
|
|
) as f:
|
2020-03-05 15:45:51 +00:00
|
|
|
yaml.dump(promtail_config, f)
|
2020-03-05 14:28:26 +00:00
|
|
|
|
2020-05-13 14:08:04 +00:00
|
|
|
os.remove(os.path.join(output_dir, "promtailLocalConfig.yaml"))
|
2020-03-05 14:28:26 +00:00
|
|
|
|
2020-04-29 15:33:51 +00:00
|
|
|
if not config["tls"]["skipVerify"]:
|
|
|
|
try:
|
|
|
|
with open(
|
|
|
|
os.path.join(output_dir, "promtail", "promtail.ca.crt"), "w"
|
|
|
|
) as f:
|
|
|
|
f.write(config["tls"]["caCert"])
|
|
|
|
except TypeError:
|
|
|
|
print("CA certificate for TLS verification has to be given.")
|
2020-03-05 15:45:51 +00:00
|
|
|
|
2020-03-05 14:28:26 +00:00
|
|
|
|
2020-03-05 14:46:34 +00:00
|
|
|
def _download_promtail(output_dir):
|
|
|
|
with open(os.path.abspath("./promtail/VERSION"), "r") as f:
|
|
|
|
promtail_version = f.readlines()[0].strip()
|
|
|
|
|
|
|
|
output_dir = os.path.join(output_dir, "promtail")
|
|
|
|
output_zip = os.path.join(output_dir, "promtail.zip")
|
|
|
|
|
|
|
|
response = requests.get(
|
|
|
|
"https://github.com/grafana/loki/releases/download/v%s/promtail-linux-amd64.zip"
|
|
|
|
% promtail_version,
|
|
|
|
stream=True,
|
|
|
|
)
|
|
|
|
with open(output_zip, "wb") as f:
|
|
|
|
for chunk in response.iter_content(chunk_size=512):
|
|
|
|
f.write(chunk)
|
|
|
|
|
|
|
|
with zipfile.ZipFile(output_zip) as f:
|
|
|
|
f.extractall(output_dir)
|
|
|
|
|
|
|
|
promtail_exe = os.path.join(output_dir, "promtail-linux-amd64")
|
|
|
|
os.chmod(
|
|
|
|
promtail_exe,
|
|
|
|
os.stat(promtail_exe).st_mode | stat.S_IEXEC | stat.S_IXGRP | stat.S_IXOTH,
|
|
|
|
)
|
|
|
|
os.remove(output_zip)
|
|
|
|
|
|
|
|
|
2020-03-13 08:39:41 +00:00
|
|
|
def _run_ytt(config, output_dir):
|
|
|
|
config_string = "#@data/values\n---\n"
|
|
|
|
config_string += yaml.dump(config)
|
|
|
|
|
|
|
|
command = [
|
|
|
|
"ytt",
|
|
|
|
]
|
|
|
|
|
|
|
|
for template in TEMPLATES:
|
|
|
|
command += ["-f", template]
|
|
|
|
|
|
|
|
command += [
|
2020-06-19 15:32:19 +00:00
|
|
|
"--output-files",
|
2020-03-13 08:39:41 +00:00
|
|
|
output_dir,
|
|
|
|
"--ignore-unknown-comments",
|
|
|
|
"-f",
|
|
|
|
"-",
|
|
|
|
]
|
|
|
|
|
|
|
|
try:
|
|
|
|
# pylint: disable=E1123
|
|
|
|
print(subprocess.check_output(command, input=config_string, text=True))
|
|
|
|
except subprocess.CalledProcessError as err:
|
|
|
|
print(err.output)
|
|
|
|
|
|
|
|
|
|
|
|
def _update_helm_repos():
|
|
|
|
for repo, url in HELM_REPOS.items():
|
|
|
|
command = ["helm", "repo", "add", repo, url]
|
|
|
|
try:
|
|
|
|
subprocess.check_output(" ".join(command), shell=True)
|
|
|
|
except subprocess.CalledProcessError as err:
|
|
|
|
print(err.output)
|
|
|
|
try:
|
|
|
|
print(subprocess.check_output(["helm", "repo", "update"]).decode("utf-8"))
|
|
|
|
except subprocess.CalledProcessError as err:
|
|
|
|
print(err.output)
|
|
|
|
|
|
|
|
|
|
|
|
def _deploy_loose_resources(output_dir):
|
|
|
|
for resource in LOOSE_RESOURCES:
|
|
|
|
command = [
|
|
|
|
"kubectl",
|
|
|
|
"apply",
|
|
|
|
"-f",
|
|
|
|
f"{output_dir}/{resource}",
|
|
|
|
]
|
|
|
|
print(subprocess.check_output(command).decode("utf-8"))
|
|
|
|
|
|
|
|
|
|
|
|
def _get_installed_charts_in_namespace(namespace):
|
|
|
|
command = ["helm", "ls", "-n", namespace, "--short"]
|
|
|
|
return subprocess.check_output(command).decode("utf-8").split("\n")
|
|
|
|
|
|
|
|
|
|
|
|
def _install_or_update_charts(output_dir, namespace):
|
|
|
|
installed_charts = _get_installed_charts_in_namespace(namespace)
|
|
|
|
charts_path = os.path.abspath("./charts")
|
|
|
|
for chart, repo in HELM_CHARTS.items():
|
|
|
|
chart_name = chart + "-" + namespace
|
|
|
|
with open(f"{charts_path}/{chart}/VERSION", "r") as f:
|
|
|
|
chart_version = f.readlines()[0].strip()
|
|
|
|
command = ["helm"]
|
|
|
|
command.append("upgrade" if chart_name in installed_charts else "install")
|
|
|
|
command += [
|
|
|
|
chart_name,
|
|
|
|
repo,
|
|
|
|
"--version",
|
|
|
|
chart_version,
|
|
|
|
"--values",
|
|
|
|
f"{output_dir}/{chart}.yaml",
|
|
|
|
"--namespace",
|
|
|
|
namespace,
|
|
|
|
]
|
|
|
|
try:
|
|
|
|
print(subprocess.check_output(command).decode("utf-8"))
|
|
|
|
except subprocess.CalledProcessError as err:
|
|
|
|
print(err.output)
|
|
|
|
|
|
|
|
|
|
|
|
def install(config_manager, output_dir, dryrun, update_repo):
|
|
|
|
"""Create the final configuration for the helm charts and Kubernetes resources
|
|
|
|
and install them to Kubernetes, if not run in --dryrun mode.
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
config_manager {AbstractConfigManager} -- ConfigManager that contains the
|
|
|
|
configuration of the monitoring setup to be uninstalled.
|
|
|
|
output_dir {string} -- Path to the directory where the generated files
|
|
|
|
should be safed in
|
|
|
|
dryrun {boolean} -- Whether the installation will be run in dryrun mode
|
|
|
|
update_repo {boolean} -- Whether to update the helm repositories locally
|
|
|
|
"""
|
2020-03-05 15:45:51 +00:00
|
|
|
config = config_manager.get_config()
|
2020-05-20 13:58:21 +00:00
|
|
|
|
|
|
|
if not os.path.exists(output_dir):
|
|
|
|
os.mkdir(output_dir)
|
2020-06-19 15:32:19 +00:00
|
|
|
elif os.listdir(output_dir):
|
|
|
|
while True:
|
|
|
|
response = input(
|
|
|
|
(
|
|
|
|
"Output directory already exists. This may lead to file conflicts "
|
|
|
|
"and unwanted configuration applied to the cluster. Do you want "
|
|
|
|
"to empty the directory? [y/n] "
|
|
|
|
)
|
|
|
|
)
|
|
|
|
if response == "y":
|
|
|
|
shutil.rmtree(output_dir)
|
|
|
|
os.mkdir(output_dir)
|
|
|
|
break
|
|
|
|
if response == "n":
|
|
|
|
print("Aborting installation. Please provide empty directory.")
|
|
|
|
sys.exit(1)
|
|
|
|
print("Unknown input.")
|
2020-05-20 13:58:21 +00:00
|
|
|
|
2020-03-05 15:45:51 +00:00
|
|
|
_run_ytt(config, output_dir)
|
2020-03-13 08:39:41 +00:00
|
|
|
|
|
|
|
namespace = config_manager.get_config()["namespace"]
|
|
|
|
_create_dashboard_configmaps(output_dir, namespace)
|
|
|
|
|
2020-05-13 14:08:04 +00:00
|
|
|
if os.path.exists(os.path.join(output_dir, "promtailLocalConfig.yaml")):
|
2020-04-29 15:33:42 +00:00
|
|
|
_create_promtail_configs(config, output_dir)
|
|
|
|
if not dryrun:
|
|
|
|
_download_promtail(output_dir)
|
2020-03-05 14:28:26 +00:00
|
|
|
|
2020-03-13 08:39:41 +00:00
|
|
|
if not dryrun:
|
|
|
|
if update_repo:
|
|
|
|
_update_helm_repos()
|
|
|
|
_deploy_loose_resources(output_dir)
|
|
|
|
_install_or_update_charts(output_dir, namespace)
|