INFRA-3304 | Dhruv | Uploads aws policy file to a channel and uses the permaLink for jit
This commit is contained in:
@@ -130,6 +130,10 @@
|
|||||||
"name": "JIT_COMMON_CHANNEL",
|
"name": "JIT_COMMON_CHANNEL",
|
||||||
"value": "$JIT_COMMON_CHANNEL"
|
"value": "$JIT_COMMON_CHANNEL"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "JIT_POLICY_UPLOAD_CHANNEL",
|
||||||
|
"value": "$JIT_POLICY_UPLOAD_CHANNEL"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "GITHUB_CLOUD_OAUTH_TOKEN",
|
"name": "GITHUB_CLOUD_OAUTH_TOKEN",
|
||||||
"value": "$GITHUB_CLOUD_OAUTH_TOKEN"
|
"value": "$GITHUB_CLOUD_OAUTH_TOKEN"
|
||||||
|
|||||||
@@ -100,6 +100,10 @@
|
|||||||
{
|
{
|
||||||
"name": "JIT_COMMON_CHANNEL",
|
"name": "JIT_COMMON_CHANNEL",
|
||||||
"value": "$JIT_COMMON_CHANNEL"
|
"value": "$JIT_COMMON_CHANNEL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "JIT_POLICY_UPLOAD_CHANNEL",
|
||||||
|
"value": "$JIT_POLICY_UPLOAD_CHANNEL"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"namespace": "$NAMESPACE",
|
"namespace": "$NAMESPACE",
|
||||||
|
|||||||
@@ -142,12 +142,15 @@ class JitServiceImpl implements JitService {
|
|||||||
|
|
||||||
private void postReviewerDmOnSlack(
|
private void postReviewerDmOnSlack(
|
||||||
User reviewer,
|
User reviewer,
|
||||||
|
String policyPermaLink,
|
||||||
JitApproval jitApproval,
|
JitApproval jitApproval,
|
||||||
SlackBotAttachment reviewMessage
|
SlackBotAttachment reviewMessage
|
||||||
) throws IOException {
|
) throws IOException {
|
||||||
var result = slackBotClient.postMessage(userService.getUsersSlackId(reviewer),
|
var channelId = userService.getUsersSlackId(reviewer);
|
||||||
reviewMessage);
|
var result = slackBotClient.postMessage(channelId, reviewMessage);
|
||||||
|
if (policyPermaLink != null) {
|
||||||
|
slackBotClient.postPermaLinkMessage(channelId, policyPermaLink);
|
||||||
|
}
|
||||||
jitApproval.setReviewerSlackMessageTimestamp(result.getTs());
|
jitApproval.setReviewerSlackMessageTimestamp(result.getTs());
|
||||||
jitApproval.setBotChannelId(result.getChannel());
|
jitApproval.setBotChannelId(result.getChannel());
|
||||||
jitApprovalsRepository.save(jitApproval);
|
jitApprovalsRepository.save(jitApproval);
|
||||||
@@ -163,11 +166,14 @@ class JitServiceImpl implements JitService {
|
|||||||
|
|
||||||
private void postRequestorDmOnSlack(
|
private void postRequestorDmOnSlack(
|
||||||
JitRequest jitRequest,
|
JitRequest jitRequest,
|
||||||
|
String policyPermaLink,
|
||||||
SlackBotAttachment personalMessage
|
SlackBotAttachment personalMessage
|
||||||
) throws IOException {
|
) throws IOException {
|
||||||
var result = slackBotClient.postMessage(
|
var channelId = userService.getUsersSlackId(jitRequest.getRequestedFor());
|
||||||
userService.getUsersSlackId(jitRequest.getRequestedFor()), personalMessage);
|
var result = slackBotClient.postMessage(channelId, personalMessage);
|
||||||
|
if (policyPermaLink != null) {
|
||||||
|
slackBotClient.postPermaLinkMessage(channelId, policyPermaLink);
|
||||||
|
}
|
||||||
jitRequest.setRequestorSlackMessageTimestamp(result.getTs());
|
jitRequest.setRequestorSlackMessageTimestamp(result.getTs());
|
||||||
jitRequest.setBotChannelId(result.getChannel());
|
jitRequest.setBotChannelId(result.getChannel());
|
||||||
}
|
}
|
||||||
@@ -198,9 +204,13 @@ class JitServiceImpl implements JitService {
|
|||||||
|
|
||||||
private void postChannelOnSlack(
|
private void postChannelOnSlack(
|
||||||
JitRequest jitRequest,
|
JitRequest jitRequest,
|
||||||
|
String policyPermaLink,
|
||||||
SlackBotAttachment commonChannelMessage
|
SlackBotAttachment commonChannelMessage
|
||||||
) throws IOException {
|
) throws IOException {
|
||||||
var result = slackBotClient.postMessage(commonChannelId, commonChannelMessage);
|
var result = slackBotClient.postMessage(commonChannelId, commonChannelMessage);
|
||||||
|
if (policyPermaLink != null) {
|
||||||
|
slackBotClient.postPermaLinkMessage(commonChannelId, policyPermaLink);
|
||||||
|
}
|
||||||
jitRequest.setChannelSlackMessageTimestamp(result.getTs());
|
jitRequest.setChannelSlackMessageTimestamp(result.getTs());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,13 +369,13 @@ class JitServiceImpl implements JitService {
|
|||||||
actionEnabled = false;
|
actionEnabled = false;
|
||||||
}
|
}
|
||||||
if (reviewerEmail.equals(onCallApproverEmail)) {
|
if (reviewerEmail.equals(onCallApproverEmail)) {
|
||||||
postRequestorDmOnSlack(jitRequest,
|
postRequestorDmOnSlack(jitRequest, null,
|
||||||
slackBotUtil.getRequestorDm(jitRequest, false, Collections.emptyList(),
|
slackBotUtil.getRequestorDm(jitRequest, false, Collections.emptyList(),
|
||||||
Collections.emptyList(), Collections.emptyList(),
|
Collections.emptyList(), Collections.emptyList(),
|
||||||
Collections.emptyList(),
|
Collections.emptyList(),
|
||||||
Collections.emptyList(),
|
Collections.emptyList(),
|
||||||
SlackColor.APPROVED));
|
SlackColor.APPROVED));
|
||||||
postChannelOnSlack(jitRequest,
|
postChannelOnSlack(jitRequest, null,
|
||||||
slackBotUtil.getChannelMessage(jitRequest, Collections.emptyList(),
|
slackBotUtil.getChannelMessage(jitRequest, Collections.emptyList(),
|
||||||
Collections.emptyList(), Collections.emptyList(),
|
Collections.emptyList(), Collections.emptyList(),
|
||||||
Collections.emptyList(),
|
Collections.emptyList(),
|
||||||
@@ -616,15 +626,19 @@ class JitServiceImpl implements JitService {
|
|||||||
});
|
});
|
||||||
pendingTeams.sort(String::compareTo);
|
pendingTeams.sort(String::compareTo);
|
||||||
|
|
||||||
|
final String uploadedPolicyPermaLink = jitRequest.getAwsPolicy() != null
|
||||||
|
? slackBotClient.sendAwsPolicyDocumentToCommonChannel(
|
||||||
|
objectMapper.writeValueAsString(jitRequest.getAwsPolicy()))
|
||||||
|
: null;
|
||||||
// send personal message to user with details on pending and approved reviewers
|
// send personal message to user with details on pending and approved reviewers
|
||||||
JitRequest jitRequestWithId = jitRequestRepository.save(jitRequest);
|
JitRequest jitRequestWithId = jitRequestRepository.save(jitRequest);
|
||||||
postRequestorDmOnSlack(jitRequestWithId,
|
postRequestorDmOnSlack(jitRequestWithId, uploadedPolicyPermaLink,
|
||||||
slackBotUtil.getRequestorDm(jitRequestWithId, true, pendingTeams,
|
slackBotUtil.getRequestorDm(jitRequestWithId, true, pendingTeams,
|
||||||
new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>(),
|
new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>(),
|
||||||
SlackColor.INFO));
|
SlackColor.INFO));
|
||||||
|
|
||||||
// send group message to common channel with details on pending and approved reviewers
|
// send group message to common channel with details on pending and approved reviewers
|
||||||
postChannelOnSlack(jitRequest,
|
postChannelOnSlack(jitRequest, uploadedPolicyPermaLink,
|
||||||
slackBotUtil.getChannelMessage(jitRequest, pendingTeams, new ArrayList<>(),
|
slackBotUtil.getChannelMessage(jitRequest, pendingTeams, new ArrayList<>(),
|
||||||
new ArrayList<>(),
|
new ArrayList<>(),
|
||||||
new ArrayList<>(), new ArrayList<>(), SlackColor.INFO));
|
new ArrayList<>(), new ArrayList<>(), SlackColor.INFO));
|
||||||
@@ -633,8 +647,8 @@ class JitServiceImpl implements JitService {
|
|||||||
JitRequest finalJitRequest = jitRequest;
|
JitRequest finalJitRequest = jitRequest;
|
||||||
jitApprovalsWithId.stream().forEach(jitApproval -> {
|
jitApprovalsWithId.stream().forEach(jitApproval -> {
|
||||||
try {
|
try {
|
||||||
postReviewerDmOnSlack(jitApproval.getReviewer(), jitApproval,
|
postReviewerDmOnSlack(jitApproval.getReviewer(), uploadedPolicyPermaLink,
|
||||||
slackBotUtil.getReviewerDm(finalJitRequest.getRequestedFor().getEmail(),
|
jitApproval, slackBotUtil.getReviewerDm(finalJitRequest.getRequestedFor().getEmail(),
|
||||||
finalJitRequest, jitApproval, true, SlackColor.INFO));
|
finalJitRequest, jitApproval, true, SlackColor.INFO));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
|
|||||||
@@ -20,4 +20,5 @@ public class JitUtil {
|
|||||||
jitRequestDto.setTeam(jitTeamOverrideMap.get(team));
|
jitRequestDto.setTeam(jitTeamOverrideMap.get(team));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,23 +22,6 @@ import org.springframework.stereotype.Component;
|
|||||||
@Component
|
@Component
|
||||||
public class SlackBotUtil {
|
public class SlackBotUtil {
|
||||||
|
|
||||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
|
||||||
|
|
||||||
private String prettyPrintJson(Map<String, Object> json) {
|
|
||||||
try {
|
|
||||||
ObjectWriter writer = objectMapper.writerWithDefaultPrettyPrinter();
|
|
||||||
return writer.writeValueAsString(json);
|
|
||||||
} catch (Exception e) {
|
|
||||||
return json.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private SlackMessageText createJsonTextBoxField(String title, Map<String, Object> json) {
|
|
||||||
String formattedJson = prettyPrintJson(json);
|
|
||||||
String formattedText = String.format("*%s*\n```%s```", title, formattedJson);
|
|
||||||
return new SlackMessageText(SlackMessageTextType.MARKDOWN, formattedText);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private SlackMessageText createTextBoxField(String title, String text) {
|
private SlackMessageText createTextBoxField(String title, String text) {
|
||||||
return new SlackMessageText(SlackMessageTextType.MARKDOWN,
|
return new SlackMessageText(SlackMessageTextType.MARKDOWN,
|
||||||
@@ -180,12 +163,6 @@ public class SlackBotUtil {
|
|||||||
style, value, actionId);
|
style, value, actionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
SlackBotMessageBlock generateAwsPolicyBlocks(Map<String, Object> awsPolicy) {
|
|
||||||
SlackMessageText awsPolicyText = createJsonTextBoxField("POLICY", awsPolicy);
|
|
||||||
return new SlackBotMessageBlock(
|
|
||||||
SlackMessageBlockType.SECTION, awsPolicyText, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public SlackBotAttachment getReviewerDm(
|
public SlackBotAttachment getReviewerDm(
|
||||||
String userEmail, JitRequest jitRequest, JitApproval jitApproval,
|
String userEmail, JitRequest jitRequest, JitApproval jitApproval,
|
||||||
boolean actionEnabled, SlackColor color
|
boolean actionEnabled, SlackColor color
|
||||||
@@ -204,9 +181,6 @@ public class SlackBotUtil {
|
|||||||
|
|
||||||
ArrayList<SlackBotMessageBlock> blocks = new ArrayList<>();
|
ArrayList<SlackBotMessageBlock> blocks = new ArrayList<>();
|
||||||
blocks.add(reviewRequestSection);
|
blocks.add(reviewRequestSection);
|
||||||
if (jitRequest.getAwsPolicy() != null) {
|
|
||||||
blocks.add(generateAwsPolicyBlocks(jitRequest.getAwsPolicy()));
|
|
||||||
}
|
|
||||||
if (reviewRequestAction != null) {
|
if (reviewRequestAction != null) {
|
||||||
blocks.add(reviewRequestAction);
|
blocks.add(reviewRequestAction);
|
||||||
}
|
}
|
||||||
@@ -242,9 +216,6 @@ public class SlackBotUtil {
|
|||||||
blocks.add(reviewRequestSection);
|
blocks.add(reviewRequestSection);
|
||||||
blocks.add(dividerSection);
|
blocks.add(dividerSection);
|
||||||
blocks.add(requestDetailSection);
|
blocks.add(requestDetailSection);
|
||||||
if (jitRequest.getAwsPolicy() != null) {
|
|
||||||
blocks.add(generateAwsPolicyBlocks(jitRequest.getAwsPolicy()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (actionEnabled) {
|
if (actionEnabled) {
|
||||||
ArrayList<SlackMessageElement> elements = new ArrayList<>();
|
ArrayList<SlackMessageElement> elements = new ArrayList<>();
|
||||||
@@ -325,9 +296,6 @@ public class SlackBotUtil {
|
|||||||
blocks.add(infoSection);
|
blocks.add(infoSection);
|
||||||
blocks.add(dividerSection);
|
blocks.add(dividerSection);
|
||||||
blocks.add(reviewRequestSection);
|
blocks.add(reviewRequestSection);
|
||||||
if (jitRequest.getAwsPolicy() != null) {
|
|
||||||
blocks.add(generateAwsPolicyBlocks(jitRequest.getAwsPolicy()));
|
|
||||||
}
|
|
||||||
return new SlackBotAttachment(color.color, blocks);
|
return new SlackBotAttachment(color.color, blocks);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,14 +5,18 @@ import com.slack.api.methods.MethodsClient;
|
|||||||
import com.slack.api.methods.SlackApiException;
|
import com.slack.api.methods.SlackApiException;
|
||||||
import com.slack.api.methods.response.chat.ChatPostMessageResponse;
|
import com.slack.api.methods.response.chat.ChatPostMessageResponse;
|
||||||
import com.slack.api.methods.response.files.FilesUploadResponse;
|
import com.slack.api.methods.response.files.FilesUploadResponse;
|
||||||
import com.slack.api.model.File;
|
import com.slack.api.methods.response.files.FilesUploadV2Response;
|
||||||
|
import com.slack.api.model.Attachment;
|
||||||
import com.slack.api.model.User;
|
import com.slack.api.model.User;
|
||||||
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.io.FileUtils;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
@@ -25,9 +29,10 @@ public class SlackBotClient {
|
|||||||
|
|
||||||
private static final String SLACK_API_URL = "https://api.slack.com/methods/";
|
private static final String SLACK_API_URL = "https://api.slack.com/methods/";
|
||||||
private static final String postMessage = "chat.postMessage";
|
private static final String postMessage = "chat.postMessage";
|
||||||
|
@Value("${jit.slack.policy.upload.channel.id}") String policyUploadChannelId;
|
||||||
|
|
||||||
@Value("${slackbot.token}")
|
@Value("${slackbot.token}")
|
||||||
private String slackBotToken;
|
private String slackBotToken;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private MethodsClient client;
|
private MethodsClient client;
|
||||||
|
|
||||||
@@ -53,6 +58,38 @@ public class SlackBotClient {
|
|||||||
return userSlackIdMap;
|
return userSlackIdMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String sendAwsPolicyDocumentToCommonChannel(
|
||||||
|
String awsPolicyDocument
|
||||||
|
) throws IOException {
|
||||||
|
FilesUploadResponse uploadResponse = null;
|
||||||
|
try {
|
||||||
|
uploadResponse = client.filesUpload(req -> req
|
||||||
|
.token(slackBotToken)
|
||||||
|
.content(awsPolicyDocument)
|
||||||
|
.channels(List.of(policyUploadChannelId))
|
||||||
|
.filename("AwsPolicyDocument.json")
|
||||||
|
.filetype("json")
|
||||||
|
);
|
||||||
|
var policyPermaLink = uploadResponse.getFile().getPermalink();
|
||||||
|
var result = client.chatPostMessage(r -> r
|
||||||
|
.token(slackBotToken)
|
||||||
|
.channel(policyUploadChannelId)
|
||||||
|
.attachments(List.of(
|
||||||
|
Attachment.builder()
|
||||||
|
.title("AWS Policy Document")
|
||||||
|
.titleLink(policyPermaLink)
|
||||||
|
.build()
|
||||||
|
))
|
||||||
|
);
|
||||||
|
if (!result.isOk()) {
|
||||||
|
throw new IOException("Unable to send policy document : " + result.getError());
|
||||||
|
}
|
||||||
|
return policyPermaLink;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IOException("Unable to upload policy document : " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public ChatPostMessageResponse postMessage(
|
public ChatPostMessageResponse postMessage(
|
||||||
String channelId,
|
String channelId,
|
||||||
SlackBotAttachment slackBotAttachment
|
SlackBotAttachment slackBotAttachment
|
||||||
@@ -70,14 +107,37 @@ public class SlackBotClient {
|
|||||||
.attachmentsAsString(textJson)
|
.attachmentsAsString(textJson)
|
||||||
);
|
);
|
||||||
if (!result.isOk()) {
|
if (!result.isOk()) {
|
||||||
log.error("Unable to process Slack API request: {}", result.getError());
|
throw new IOException("Unable to process Slack API request: " + result.getError());
|
||||||
}
|
}
|
||||||
} catch (IOException | SlackApiException e) {
|
} catch (Exception e) {
|
||||||
log.error("error: {}", e.getMessage(), e);
|
throw new IOException("Unable to process Slack API request: " + e.getMessage());
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ChatPostMessageResponse postPermaLinkMessage(
|
||||||
|
String channelId,
|
||||||
|
String policyPermaLink
|
||||||
|
) throws IOException {
|
||||||
|
try {
|
||||||
|
var result = client.chatPostMessage(r -> r
|
||||||
|
.token(slackBotToken)
|
||||||
|
.channel(channelId)
|
||||||
|
.attachments(List.of(
|
||||||
|
Attachment.builder().title("AWS Policy Document").titleLink(policyPermaLink)
|
||||||
|
.build()
|
||||||
|
))
|
||||||
|
);
|
||||||
|
if (!result.isOk()) {
|
||||||
|
throw new IOException("Unable to process Slack API request: " + result.getError());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IOException("Unable to process Slack API request: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public void updateMessage(String channelId, SlackBotAttachment slackBotAttachment, String ts)
|
public void updateMessage(String channelId, SlackBotAttachment slackBotAttachment, String ts)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ slackbot.token=${SLACK_BOT_TOKEN:xoxb-format-12345}
|
|||||||
jit.dag.id=${JIT_DAG_ID:jit_dag}
|
jit.dag.id=${JIT_DAG_ID:jit_dag}
|
||||||
jit.oncall-approver.email=jit-slackbot@jit.com
|
jit.oncall-approver.email=jit-slackbot@jit.com
|
||||||
jit.slack.common.channel.id=${JIT_COMMON_CHANNEL:C06NDTBFA1G}
|
jit.slack.common.channel.id=${JIT_COMMON_CHANNEL:C06NDTBFA1G}
|
||||||
|
jit.slack.policy.upload.channel.id=${JIT_POLICY_UPLOAD_CHANNEL:C0000000000}
|
||||||
jit.request.config.path=classpath:jit
|
jit.request.config.path=classpath:jit
|
||||||
jit.team-override.map={'Kubernetes Platform': 'Infra'}
|
jit.team-override.map={'Kubernetes Platform': 'Infra'}
|
||||||
#pipeline creation
|
#pipeline creation
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ jit.dag.id=${JIT_DAG_ID:jit_dag}
|
|||||||
jit.gocd.dag.id=${JIT_GOCD_DAG_ID:jit_gocd_dag}
|
jit.gocd.dag.id=${JIT_GOCD_DAG_ID:jit_gocd_dag}
|
||||||
jit.oncall-approver.email=jit-slackbot@jit.com
|
jit.oncall-approver.email=jit-slackbot@jit.com
|
||||||
jit.slack.common.channel.id=${JIT_COMMON_CHANNEL:C0000000000}
|
jit.slack.common.channel.id=${JIT_COMMON_CHANNEL:C0000000000}
|
||||||
|
jit.slack.policy.upload.channel.id=${JIT_POLICY_UPLOAD_CHANNEL:C0000000000}
|
||||||
github.token=${GITHUB_CLOUD_OAUTH_TOKEN}
|
github.token=${GITHUB_CLOUD_OAUTH_TOKEN}
|
||||||
gocd.pipelines.config=${GOCD_PIPELINES_CONFIG}
|
gocd.pipelines.config=${GOCD_PIPELINES_CONFIG}
|
||||||
vertical.owner.map={'lending':{'navi-data-science':'navi-data-science','default':'navi-medici'},'insurance':{'default':'navi-gi'},'sa':{'default':'navi-sa'},'amc':{'default':'navi-amc'},'navi-pay':{'default':'navi-pay'},'navi-ppl':{'default':'navi-ppl'}}
|
vertical.owner.map={'lending':{'navi-data-science':'navi-data-science','default':'navi-medici'},'insurance':{'default':'navi-gi'},'sa':{'default':'navi-sa'},'amc':{'default':'navi-amc'},'navi-pay':{'default':'navi-pay'},'navi-ppl':{'default':'navi-ppl'}}
|
||||||
|
|||||||
Reference in New Issue
Block a user