forked from Perth-Artifactory/taiga_sync
-
Notifications
You must be signed in to change notification settings - Fork 0
/
sync_board_membership.py
173 lines (147 loc) · 6.11 KB
/
sync_board_membership.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
import requests
import logging
import json
import sys
from slack_bolt import App
from datetime import datetime
from pprint import pprint, pformat
import time
from taiga import TaigaAPI
from util import tidyhq, taigalink
from slack import misc as slack_misc
# Set up logging
logging.basicConfig(level=logging.INFO)
# Set urllib3 logging level to INFO to reduce noise when individual modules are set to debug
urllib3_logger = logging.getLogger("urllib3")
urllib3_logger.setLevel(logging.INFO)
# Set slack bolt logging level to INFO to reduce noise when individual modules are set to debug
slack_logger = logging.getLogger("slack")
slack_logger.setLevel(logging.INFO)
setup_logger = logging.getLogger("setup")
logger = logging.getLogger("issue_sync")
# Load config
try:
with open("config.json") as f:
config = json.load(f)
except FileNotFoundError:
setup_logger.error(
"config.json not found. Create it using example.config.json as a template"
)
sys.exit(1)
if not config["taiga"].get("auth_token"):
# Get auth token for Taiga
# This is used instead of python-taiga's inbuilt user/pass login method since we also need to interact with the api directly
auth_url = f"{config['taiga']['url']}/api/v1/auth"
auth_data = {
"password": config["taiga"]["password"],
"type": "normal",
"username": config["taiga"]["username"],
}
response = requests.post(
auth_url,
headers={"Content-Type": "application/json"},
data=json.dumps(auth_data),
)
if response.status_code == 200:
taiga_auth_token = response.json().get("auth_token")
else:
setup_logger.error(f"Failed to get auth token: {response.status_code}")
sys.exit(1)
else:
taiga_auth_token = config["taiga"]["auth_token"]
taigacon = TaigaAPI(host=config["taiga"]["url"], token=taiga_auth_token)
# Set up TidyHQ cache
tidyhq_cache = tidyhq.fresh_cache(config=config)
setup_logger.info(
f"TidyHQ cache set up: {len(tidyhq_cache['contacts'])} contacts, {len(tidyhq_cache['groups'])} groups"
)
# Set up Taiga cache
taiga_cache = taigalink.setup_cache(
config=config, taiga_auth_token=taiga_auth_token, taigacon=taigacon
)
# Connect to Slack
app = App(token=config["slack"]["bot_token"], logger=slack_logger)
# Check over slack channels and look for ones that have corresponding boards
slack_channels = app.client.conversations_list(
types="public_channel,private_channel", exclude_archived=True, limit=1000
)["channels"]
# Sort channels by name
slack_channels = sorted(slack_channels, key=lambda x: x["name"])
for channel in slack_channels:
if not channel["is_member"]:
logger.debug(f"We are not a member of channel {channel['name']}, skipping")
continue
logger.debug(f"Checking channel {channel['name']}")
for c_project_id, c_channel_id in config["taiga-channel"].items():
if c_channel_id == channel["id"]:
print("------------------")
project_id = int(c_project_id)
logger.info(
f"Found channel #{channel['name']} in config. Maps to Taiga project {taiga_cache['boards'][project_id]['name']}"
)
break
else:
logger.debug(f"Channel {channel['name']} not in config")
continue
# Get the channel's members
channel_members = app.client.conversations_members(channel=channel["id"])["members"]
# Check if any of the channel's members exist in taiga_users
for slack_id in channel_members:
taiga_id = tidyhq.map_slack_to_taiga(
tidyhq_cache=tidyhq_cache, slack_id=slack_id, config=config
)
if not taiga_id:
logger.debug(f"No Taiga ID found for user {slack_id}")
continue
# Check if the slack user is a member of the project
if taiga_id in taiga_cache["boards"][project_id]["members"]:
logger.info(
f"User {slack_misc.name_mapper(slack_id=slack_id, slack_app=app)} ({slack_id}) is already a member of project: {taiga_cache['boards'][project_id]['name']}"
)
continue
else:
logger.info(
f"User {slack_misc.name_mapper(slack_id=slack_id, slack_app=app)} ({slack_id}) is not a member of project: {taiga_cache['boards'][project_id]['name']} and may need to be added"
)
private = False
# Check if the board is private
if taiga_cache["boards"][project_id]["private"]:
logger.info(
f"Project {taiga_cache['boards'][project_id]['name']} is private"
)
# Check if the channel is also private
if not channel["is_private"]:
logger.info(f"Channel #{channel['name']} not private, will not add")
continue
logger.info("Both the board and channel are private, will add")
role_key = "highest_role"
private = True
else:
logger.info(
f"Project {taiga_cache['boards'][project_id]['name']} is public, will add"
)
role_key = "lowest_role"
logger.info(
f"Adding {slack_misc.name_mapper(slack_id=slack_id, slack_app=app)}/{taiga_cache['users'][taiga_id]['name']} to project {taiga_cache['boards'][project_id]['name']} (ID:{project_id})"
)
logger.info(
f"Adding as {'lowest' if not private else 'highest'} role in project ({taiga_cache['boards'][project_id][role_key]['name']})"
)
response = requests.post(
f"{config['taiga']['url']}/api/v1/memberships",
headers={
"Authorization": f"Bearer {taiga_auth_token}",
"Content-Type": "application/json",
},
data=json.dumps(
{
"role": taiga_cache["boards"][project_id][role_key]["id"],
"project": project_id,
"username": taiga_cache["users"][taiga_id]["username"],
}
),
)
if response.status_code == 201:
logger.info(
f"Added {slack_id}/{taiga_id} to {taiga_cache['boards'][project_id]['name']}"
)