-
Notifications
You must be signed in to change notification settings - Fork 261
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add Splunk OnCall (aka VictorOps) migration script
- Loading branch information
1 parent
ccdf991
commit 5cf5f64
Showing
61 changed files
with
3,345 additions
and
381 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
254 changes: 209 additions & 45 deletions
254
tools/pagerduty-migrator/README.md → tools/migrators/README.md
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import os | ||
import sys | ||
|
||
from pdpyras import APISession | ||
|
||
from lib.grafana.api_client import GrafanaAPIClient | ||
from lib.splunk.api_client import SplunkOnCallAPIClient | ||
|
||
MIGRATING_FROM = os.environ["MIGRATING_FROM"] | ||
PAGERDUTY = "pagerduty" | ||
SPLUNK = "splunk" | ||
|
||
PAGERDUTY_API_TOKEN = os.environ.get("PAGERDUTY_API_TOKEN") | ||
SPLUNK_API_ID = os.environ.get("SPLUNK_API_ID") | ||
SPLUNK_API_KEY = os.environ.get("SPLUNK_API_KEY") | ||
|
||
GRAFANA_URL = os.environ["GRAFANA_URL"] # Example: http://localhost:3000 | ||
GRAFANA_USERNAME = os.environ["GRAFANA_USERNAME"] | ||
GRAFANA_PASSWORD = os.environ["GRAFANA_PASSWORD"] | ||
|
||
SUCCESS_SIGN = "✅" | ||
ERROR_SIGN = "❌" | ||
|
||
grafana_client = GrafanaAPIClient(GRAFANA_URL, GRAFANA_USERNAME, GRAFANA_PASSWORD) | ||
|
||
|
||
def list_pagerduty_users(): | ||
session = APISession(PAGERDUTY_API_TOKEN) | ||
for user in session.list_all("users"): | ||
create_grafana_user(user["name"], user["email"]) | ||
|
||
|
||
def list_splunk_users(): | ||
client = SplunkOnCallAPIClient(SPLUNK_API_ID, SPLUNK_API_KEY) | ||
for user in client.fetch_users(include_paging_policies=False): | ||
create_grafana_user(f"{user['firstName']} {user['lastName']}", user["email"]) | ||
|
||
|
||
def create_grafana_user(name: str, email: str): | ||
response = grafana_client.create_user_with_random_password(name, email) | ||
|
||
if response.status_code == 200: | ||
print(SUCCESS_SIGN + " User created: " + email) | ||
elif response.status_code == 401: | ||
sys.exit(ERROR_SIGN + " Invalid username or password.") | ||
elif response.status_code == 412: | ||
print(ERROR_SIGN + " User " + email + " already exists.") | ||
else: | ||
print("{} {}".format(ERROR_SIGN, response.text)) | ||
|
||
|
||
if __name__ == "__main__": | ||
if MIGRATING_FROM == PAGERDUTY: | ||
list_pagerduty_users() | ||
elif MIGRATING_FROM == SPLUNK: | ||
list_splunk_users() | ||
else: | ||
raise ValueError("Invalid value for MIGRATING_FROM") |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import os | ||
from urllib.parse import urljoin | ||
|
||
PAGERDUTY = "pagerduty" | ||
SPLUNK = "splunk" | ||
MIGRATING_FROM = os.getenv("MIGRATING_FROM") | ||
assert MIGRATING_FROM in (PAGERDUTY, SPLUNK) | ||
|
||
MODE_PLAN = "plan" | ||
MODE_MIGRATE = "migrate" | ||
MODE = os.getenv("MODE", default=MODE_PLAN) | ||
assert MODE in (MODE_PLAN, MODE_MIGRATE) | ||
|
||
ONCALL_API_TOKEN = os.environ["ONCALL_API_TOKEN"] | ||
ONCALL_API_URL = urljoin( | ||
os.environ["ONCALL_API_URL"].removesuffix("/") + "/", | ||
"api/v1/", | ||
) | ||
ONCALL_DELAY_OPTIONS = [1, 5, 15, 30, 60] | ||
|
||
SCHEDULE_MIGRATION_MODE_ICAL = "ical" | ||
SCHEDULE_MIGRATION_MODE_WEB = "web" | ||
SCHEDULE_MIGRATION_MODE = os.getenv( | ||
"SCHEDULE_MIGRATION_MODE", SCHEDULE_MIGRATION_MODE_ICAL | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
TAB = " " * 4 | ||
SUCCESS_SIGN = "✅" | ||
ERROR_SIGN = "❌" | ||
WARNING_SIGN = "⚠️" # TODO: warning sign does not renders properly |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import typing | ||
|
||
|
||
class MatchTeam(typing.TypedDict): | ||
name: str | ||
oncall_team: typing.Optional[typing.Dict[str, typing.Any]] | ||
|
||
|
||
def match_team(team: MatchTeam, oncall_teams: typing.List[MatchTeam]) -> None: | ||
oncall_team = None | ||
for candidate_team in oncall_teams: | ||
if team["name"].lower() == candidate_team["name"].lower(): | ||
oncall_team = candidate_team | ||
break | ||
|
||
team["oncall_team"] = oncall_team |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import typing | ||
|
||
|
||
class MatchUser(typing.TypedDict): | ||
email: str | ||
oncall_user: typing.Optional[typing.Dict[str, typing.Any]] | ||
|
||
|
||
def match_user(user: MatchUser, oncall_users: typing.List[MatchUser]) -> None: | ||
oncall_user = None | ||
for candidate_user in oncall_users: | ||
if user["email"].lower() == candidate_user["email"].lower(): | ||
oncall_user = candidate_user | ||
break | ||
|
||
user["oncall_user"] = oncall_user |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
import secrets | ||
from urllib.parse import urljoin | ||
|
||
import requests | ||
|
||
|
||
class GrafanaAPIClient: | ||
def __init__(self, base_url, username, password): | ||
self.base_url = base_url | ||
self.username = username | ||
self.password = password | ||
|
||
def _api_call(self, method: str, path: str, **kwargs): | ||
return requests.request( | ||
method, | ||
urljoin(self.base_url, path), | ||
auth=(self.username, self.password), | ||
**kwargs, | ||
) | ||
|
||
def create_user_with_random_password(self, name: str, email: str): | ||
return self._api_call( | ||
"POST", | ||
"/api/admin/users", | ||
json={ | ||
"name": name, | ||
"email": email, | ||
"login": email.split("@")[0], | ||
"password": secrets.token_urlsafe(15), | ||
}, | ||
) | ||
|
||
def get_all_users(self): | ||
""" | ||
https://grafana.com/docs/grafana/v10.3/developers/http_api/user/#search-users | ||
""" | ||
return self._api_call("GET", "/api/users").json() | ||
|
||
def idemopotently_create_team_and_add_users( | ||
self, team_name: str, user_emails: list[str] | ||
) -> int: | ||
""" | ||
Get team by name | ||
https://grafana.com/docs/grafana/v10.3/developers/http_api/team/#using-the-name-parameter | ||
Create team | ||
https://grafana.com/docs/grafana/v10.3/developers/http_api/team/#add-team | ||
Add team members | ||
https://grafana.com/docs/grafana/v10.3/developers/http_api/team/#add-team-member | ||
""" | ||
existing_team = self._api_call( | ||
"GET", "/api/teams/search", params={"name": team_name} | ||
).json() | ||
|
||
if existing_team["teams"]: | ||
# team already exists | ||
team_id = existing_team["teams"][0]["id"] | ||
else: | ||
# team doesn't exist create it | ||
response = self._api_call("POST", "/api/teams", json={"name": team_name}) | ||
|
||
if response.status_code == 200: | ||
team_id = response.json()["teamId"] | ||
else: | ||
raise Exception(f"Failed to fetch/create Grafana team '{team_name}'") | ||
|
||
grafana_users = self.get_all_users() | ||
grafana_user_id_to_email_map = {} | ||
|
||
for user_email in user_emails: | ||
for grafana_user in grafana_users: | ||
if grafana_user["email"] == user_email: | ||
grafana_user_id_to_email_map[grafana_user["id"]] = user_email | ||
break | ||
|
||
for user_id in grafana_user_id_to_email_map.keys(): | ||
self._api_call( | ||
"POST", f"/api/teams/{team_id}/members", json={"userId": user_id} | ||
) | ||
|
||
return team_id |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
Oops, something went wrong.