QuickSight - list_users,list_groups,create_group_membership,create_data_set,create_ingestion (#4989)

This commit is contained in:
Bert Blommers 2022-03-31 12:26:17 +00:00 committed by GitHub
parent d6bec75e43
commit e533b1a3ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 556 additions and 21 deletions

View File

@ -4042,20 +4042,20 @@
## quicksight
<details>
<summary>6% implemented</summary>
<summary>11% implemented</summary>
- [ ] cancel_ingestion
- [ ] create_account_customization
- [ ] create_analysis
- [ ] create_dashboard
- [ ] create_data_set
- [X] create_data_set
- [ ] create_data_source
- [ ] create_folder
- [ ] create_folder_membership
- [X] create_group
- [ ] create_group_membership
- [X] create_group_membership
- [ ] create_iam_policy_assignment
- [ ] create_ingestion
- [X] create_ingestion
- [ ] create_namespace
- [ ] create_template
- [ ] create_template_alias
@ -4092,6 +4092,7 @@
- [ ] describe_folder_permissions
- [ ] describe_folder_resolved_permissions
- [X] describe_group
- [X] describe_group_membership
- [ ] describe_iam_policy_assignment
- [ ] describe_ingestion
- [ ] describe_ip_restriction
@ -4114,8 +4115,8 @@
- [ ] list_data_sources
- [ ] list_folder_members
- [ ] list_folders
- [ ] list_group_memberships
- [ ] list_groups
- [X] list_group_memberships
- [X] list_groups
- [ ] list_iam_policy_assignments
- [ ] list_iam_policy_assignments_for_user
- [ ] list_ingestions
@ -4128,12 +4129,13 @@
- [ ] list_theme_versions
- [ ] list_themes
- [ ] list_user_groups
- [ ] list_users
- [X] list_users
- [X] register_user
- [ ] restore_analysis
- [ ] search_analyses
- [ ] search_dashboards
- [ ] search_folders
- [ ] search_groups
- [ ] tag_resource
- [ ] untag_resource
- [ ] update_account_customization

View File

@ -31,14 +31,14 @@ quicksight
- [ ] create_account_customization
- [ ] create_analysis
- [ ] create_dashboard
- [ ] create_data_set
- [X] create_data_set
- [ ] create_data_source
- [ ] create_folder
- [ ] create_folder_membership
- [X] create_group
- [ ] create_group_membership
- [X] create_group_membership
- [ ] create_iam_policy_assignment
- [ ] create_ingestion
- [X] create_ingestion
- [ ] create_namespace
- [ ] create_template
- [ ] create_template_alias
@ -75,6 +75,7 @@ quicksight
- [ ] describe_folder_permissions
- [ ] describe_folder_resolved_permissions
- [X] describe_group
- [X] describe_group_membership
- [ ] describe_iam_policy_assignment
- [ ] describe_ingestion
- [ ] describe_ip_restriction
@ -97,8 +98,16 @@ quicksight
- [ ] list_data_sources
- [ ] list_folder_members
- [ ] list_folders
- [ ] list_group_memberships
- [ ] list_groups
- [X] list_group_memberships
The NextToken and MaxResults parameters are not yet implemented
- [X] list_groups
The NextToken and MaxResults parameters are not yet implemented
- [ ] list_iam_policy_assignments
- [ ] list_iam_policy_assignments_for_user
- [ ] list_ingestions
@ -111,7 +120,11 @@ quicksight
- [ ] list_theme_versions
- [ ] list_themes
- [ ] list_user_groups
- [ ] list_users
- [X] list_users
The NextToken and MaxResults parameters are not yet implemented
- [X] register_user
The following parameters are not yet implemented:
@ -122,6 +135,7 @@ quicksight
- [ ] search_analyses
- [ ] search_dashboards
- [ ] search_folders
- [ ] search_groups
- [ ] tag_resource
- [ ] untag_resource
- [ ] update_account_customization

View File

@ -5,6 +5,50 @@ from moto.core.utils import BackendDict
from .exceptions import ResourceNotFoundException
def _create_id(aws_account_id, namespace, _id):
return f"{aws_account_id}:{namespace}:{_id}"
class QuicksightDataSet(BaseModel):
def __init__(self, region, _id, name):
self.arn = f"arn:aws:quicksight:{region}:{ACCOUNT_ID}:data-set/{_id}"
self._id = _id
self.name = name
self.region = region
def to_json(self):
return {
"Arn": self.arn,
"DataSetId": self._id,
"IngestionArn": f"arn:aws:quicksight:{self.region}:{ACCOUNT_ID}:ingestion/tbd",
}
class QuicksightIngestion(BaseModel):
def __init__(self, region, data_set_id, ingestion_id):
self.arn = f"arn:aws:quicksight:{region}:{ACCOUNT_ID}:data-set/{data_set_id}/ingestions/{ingestion_id}"
self.ingestion_id = ingestion_id
def to_json(self):
return {
"Arn": self.arn,
"IngestionId": self.ingestion_id,
"IngestionStatus": "INITIALIZED",
}
class QuicksightMembership(BaseModel):
def __init__(self, region, group, user):
self.group = group
self.user = user
self.arn = (
f"arn:aws:quicksight:{region}:{ACCOUNT_ID}:group/default/{group}/{user}"
)
def to_json(self):
return {"Arn": self.arn, "MemberName": self.user}
class QuicksightGroup(BaseModel):
def __init__(self, region, group_name, description, aws_account_id, namespace):
self.arn = (
@ -14,6 +58,23 @@ class QuicksightGroup(BaseModel):
self.description = description
self.aws_account_id = aws_account_id
self.namespace = namespace
self.region = region
self.members = dict()
def add_member(self, user_name):
membership = QuicksightMembership(self.region, self.group_name, user_name)
self.members[user_name] = membership
return membership
def delete_member(self, user_name):
self.members.pop(user_name, None)
def get_member(self, user_name):
return self.members[user_name]
def list_members(self):
return self.members.values()
def to_json(self):
return {
@ -59,6 +120,9 @@ class QuickSightBackend(BaseBackend):
self.__dict__ = {}
self.__init__(region_name)
def create_data_set(self, data_set_id, name):
return QuicksightDataSet(self.region_name, data_set_id, name=name)
def create_group(self, group_name, description, aws_account_id, namespace):
group = QuicksightGroup(
region=self.region_name,
@ -67,24 +131,69 @@ class QuickSightBackend(BaseBackend):
aws_account_id=aws_account_id,
namespace=namespace,
)
self.groups[f"{aws_account_id}:{namespace}:{group_name}"] = group
_id = _create_id(aws_account_id, namespace, group_name)
self.groups[_id] = group
return group
def create_group_membership(self, aws_account_id, namespace, group_name, user_name):
group = self.describe_group(aws_account_id, namespace, group_name)
return group.add_member(user_name)
def create_ingestion(self, data_set_id, ingestion_id):
return QuicksightIngestion(self.region_name, data_set_id, ingestion_id)
def delete_group(self, aws_account_id, namespace, group_name):
self.groups.pop(f"{aws_account_id}:{namespace}:{group_name}", None)
_id = _create_id(aws_account_id, namespace, group_name)
self.groups.pop(_id, None)
def delete_user(self, aws_account_id, namespace, user_name):
self.users.pop(f"{aws_account_id}:{namespace}:{user_name}", None)
# Delete users from all groups
for group in self.groups.values():
group.delete_member(user_name)
# Delete user itself
_id = _create_id(aws_account_id, namespace, user_name)
self.users.pop(_id, None)
def describe_group(self, aws_account_id, namespace, group_name):
if f"{aws_account_id}:{namespace}:{group_name}" not in self.groups:
_id = _create_id(aws_account_id, namespace, group_name)
if _id not in self.groups:
raise ResourceNotFoundException(f"Group {group_name} not found")
return self.groups[f"{aws_account_id}:{namespace}:{group_name}"]
return self.groups[_id]
def describe_group_membership(
self, aws_account_id, namespace, group_name, user_name
):
group = self.describe_group(aws_account_id, namespace, group_name)
return group.get_member(user_name)
def describe_user(self, aws_account_id, namespace, user_name):
if f"{aws_account_id}:{namespace}:{user_name}" not in self.users:
_id = _create_id(aws_account_id, namespace, user_name)
if _id not in self.users:
raise ResourceNotFoundException(f"User {user_name} not found")
return self.users[f"{aws_account_id}:{namespace}:{user_name}"]
return self.users[_id]
def list_groups(self, aws_account_id, namespace):
"""
The NextToken and MaxResults parameters are not yet implemented
"""
id_for_ns = _create_id(aws_account_id, namespace, _id="")
return [
group for _id, group in self.groups.items() if _id.startswith(id_for_ns)
]
def list_group_memberships(self, aws_account_id, namespace, group_name):
"""
The NextToken and MaxResults parameters are not yet implemented
"""
group = self.describe_group(aws_account_id, namespace, group_name)
return group.list_members()
def list_users(self, aws_account_id, namespace):
"""
The NextToken and MaxResults parameters are not yet implemented
"""
id_for_ns = _create_id(aws_account_id, namespace, _id="")
return [user for _id, user in self.users.items() if _id.startswith(id_for_ns)]
def register_user(
self, identity_type, email, user_role, aws_account_id, namespace, user_name
@ -100,7 +209,8 @@ class QuickSightBackend(BaseBackend):
user_role=user_role,
username=user_name,
)
self.users[f"{aws_account_id}:{namespace}:{user_name}"] = user
_id = _create_id(aws_account_id, namespace, user_name)
self.users[_id] = user
return user
def update_group(self, aws_account_id, namespace, group_name, description):

View File

@ -13,10 +13,17 @@ class QuickSightResponse(BaseResponse):
"""Return backend instance specific for this region."""
return quicksight_backends[self.region]
def dataset(self, request, full_url, headers):
self.setup_class(request, full_url, headers)
if request.method == "POST":
return self.create_data_set()
def groups(self, request, full_url, headers):
self.setup_class(request, full_url, headers)
if request.method == "POST":
return self.create_group()
if request.method == "GET":
return self.list_groups()
def group(self, request, full_url, headers):
self.setup_class(request, full_url, headers)
@ -27,10 +34,29 @@ class QuickSightResponse(BaseResponse):
if request.method == "PUT":
return self.update_group()
def group_member(self, request, full_url, headers):
self.setup_class(request, full_url, headers)
if request.method == "PUT":
return self.create_group_membership()
if request.method == "GET":
return self.describe_group_membership()
def group_members(self, request, full_url, headers):
self.setup_class(request, full_url, headers)
if request.method == "GET":
return self.list_group_memberships()
def ingestion(self, request, full_url, headers):
self.setup_class(request, full_url, headers)
if request.method == "PUT":
return self.create_ingestion()
def users(self, request, full_url, headers):
self.setup_class(request, full_url, headers)
if request.method == "POST":
return self.register_user()
if request.method == "GET":
return self.list_users()
def user(self, request, full_url, headers):
self.setup_class(request, full_url, headers)
@ -39,6 +65,13 @@ class QuickSightResponse(BaseResponse):
if request.method == "DELETE":
return self.delete_user()
def create_data_set(self):
params = json.loads(self.body)
data_set_id = params.get("DataSetId")
name = params.get("Name")
data_set = self.quicksight_backend.create_data_set(data_set_id, name)
return 200, {}, json.dumps(data_set.to_json())
def create_group(self):
params = json.loads(self.body)
group_name = params.get("GroupName")
@ -53,6 +86,53 @@ class QuickSightResponse(BaseResponse):
)
return 200, {}, json.dumps(dict(Group=group.to_json()))
def create_group_membership(self):
aws_account_id = self.path.split("/")[-7]
namespace = self.path.split("/")[-5]
group_name = self.path.split("/")[-3]
user_name = self.path.split("/")[-1]
member = self.quicksight_backend.create_group_membership(
aws_account_id, namespace, group_name, user_name
)
return 200, {}, json.dumps({"GroupMember": member.to_json()})
def create_ingestion(self):
data_set_id = self.path.split("/")[-3]
ingestion_id = self.path.split("/")[-1]
ingestion = self.quicksight_backend.create_ingestion(data_set_id, ingestion_id)
return 200, {}, json.dumps(ingestion.to_json())
def describe_group_membership(self):
aws_account_id = self.path.split("/")[-7]
namespace = self.path.split("/")[-5]
group_name = self.path.split("/")[-3]
user_name = self.path.split("/")[-1]
member = self.quicksight_backend.describe_group_membership(
aws_account_id, namespace, group_name, user_name
)
return 200, {}, json.dumps({"GroupMember": member.to_json()})
def list_groups(self):
aws_account_id = self.path.split("/")[-4]
namespace = self.path.split("/")[-2]
groups = self.quicksight_backend.list_groups(aws_account_id, namespace)
return 200, {}, json.dumps(dict(GroupList=[g.to_json() for g in groups]))
def list_group_memberships(self):
aws_account_id = self.path.split("/")[-6]
namespace = self.path.split("/")[-4]
group_name = self.path.split("/")[-2]
members = self.quicksight_backend.list_group_memberships(
aws_account_id, namespace, group_name
)
return 200, {}, json.dumps({"GroupMemberList": [m.to_json() for m in members]})
def list_users(self):
aws_account_id = self.path.split("/")[-4]
namespace = self.path.split("/")[-2]
users = self.quicksight_backend.list_users(aws_account_id, namespace)
return 200, {}, json.dumps(dict(UserList=[u.to_json() for u in users]))
def register_user(self):
params = json.loads(self.body)
identity_type = params.get("IdentityType")

View File

@ -10,8 +10,12 @@ response = QuickSightResponse()
url_paths = {
r"{0}/accounts/(?P<account_id>[\d]+)/data-sets$": response.dataset,
r"{0}/accounts/(?P<account_id>[\d]+)/data-sets/(?P<datasetid>[^/.]+)/ingestions/(?P<ingestionid>[^/.]+)$": response.ingestion,
r"{0}/accounts/(?P<account_id>[\d]+)/namespaces/(?P<namespace>[^/.]+)/groups$": response.groups,
r"{0}/accounts/(?P<account_id>[\d]+)/namespaces/(?P<namespace>[^/.]+)/groups/(?P<groupname>[^/.]+)$": response.group,
r"{0}/accounts/(?P<account_id>[\d]+)/namespaces/(?P<namespace>[^/.]+)/groups/(?P<groupname>[^/.]+)/members$": response.group_members,
r"{0}/accounts/(?P<account_id>[\d]+)/namespaces/(?P<namespace>[^/.]+)/groups/(?P<groupname>[^/.]+)/members/(?P<username>[^/.]+)$": response.group_member,
r"{0}/accounts/(?P<account_id>[\d]+)/namespaces/(?P<namespace>[^/.]+)/users$": response.users,
r"{0}/accounts/(?P<account_id>[\d]+)/namespaces/(?P<namespace>[^/.]+)/users/(?P<username>[^/.]+)$": response.user,
}

View File

@ -0,0 +1,73 @@
import boto3
import sure # noqa # pylint: disable=unused-import
from moto import mock_quicksight
from moto.core import ACCOUNT_ID
# See our Development Tips on writing tests for hints on how to write good tests:
# http://docs.getmoto.org/en/latest/docs/contributing/development_tips/tests.html
@mock_quicksight
def test_create_data_set():
client = boto3.client("quicksight", region_name="eu-west-1")
resp = client.create_data_set(
AwsAccountId=ACCOUNT_ID,
DataSetId="myset",
Name="My Data Set",
ImportMode="SPICE",
PhysicalTableMap={
"table1": {
"RelationalTable": {
"DataSourceArn": "d:s:arn",
"Catalog": "cat",
"Name": "dog",
"InputColumns": [{"Name": "c1", "Type": "string"}],
}
}
},
)
resp.should.have.key("Arn").equals(
f"arn:aws:quicksight:eu-west-1:{ACCOUNT_ID}:data-set/myset"
)
resp.should.have.key("DataSetId").equals("myset")
resp.should.have.key("IngestionArn").equals(
f"arn:aws:quicksight:eu-west-1:{ACCOUNT_ID}:ingestion/tbd"
)
@mock_quicksight
def test_create_ingestion():
client = boto3.client("quicksight", region_name="eu-west-1")
client.create_data_set(
AwsAccountId=ACCOUNT_ID,
DataSetId="myset",
Name="My Data Set",
ImportMode="SPICE",
PhysicalTableMap={
"table1": {
"RelationalTable": {
"DataSourceArn": "d:s:arn",
"Catalog": "cat",
"Name": "dog",
"InputColumns": [{"Name": "c1", "Type": "string"}],
}
}
},
)
resp = client.create_ingestion(
AwsAccountId=ACCOUNT_ID,
DataSetId="n_a",
IngestionId="n_a2",
IngestionType="FULL_REFRESH",
)
resp.should.have.key("Arn").equals(
f"arn:aws:quicksight:eu-west-1:{ACCOUNT_ID}:data-set/n_a/ingestions/n_a2"
)
resp.should.have.key("IngestionId").equals("n_a2")
resp.should.have.key("IngestionStatus").equals("INITIALIZED")

View File

@ -106,3 +106,42 @@ def test_delete_group():
)
err = exc.value.response["Error"]
err["Code"].should.equal("ResourceNotFoundException")
@mock_quicksight
def test_list_groups__initial():
client = boto3.client("quicksight", region_name="us-east-2")
resp = client.list_groups(AwsAccountId=ACCOUNT_ID, Namespace="default")
resp.should.have.key("GroupList").equals([])
resp.should.have.key("Status").equals(200)
@mock_quicksight
def test_list_groups():
client = boto3.client("quicksight", region_name="us-east-1")
for i in range(4):
client.create_group(
AwsAccountId=ACCOUNT_ID, Namespace="default", GroupName=f"group{i}"
)
resp = client.list_groups(AwsAccountId=ACCOUNT_ID, Namespace="default")
resp.should.have.key("GroupList").length_of(4)
resp.should.have.key("Status").equals(200)
resp["GroupList"].should.contain(
{
"Arn": f"arn:aws:quicksight:us-east-1:{ACCOUNT_ID}:group/default/group0",
"GroupName": "group0",
"PrincipalId": ACCOUNT_ID,
}
)
resp["GroupList"].should.contain(
{
"Arn": f"arn:aws:quicksight:us-east-1:{ACCOUNT_ID}:group/default/group3",
"GroupName": "group3",
"PrincipalId": ACCOUNT_ID,
}
)

View File

@ -86,3 +86,216 @@ def test_delete_user__quicksight():
)
err = exc.value.response["Error"]
err["Code"].should.equal("ResourceNotFoundException")
@mock_quicksight
def test_list_users__initial():
client = boto3.client("quicksight", region_name="us-east-2")
resp = client.list_users(AwsAccountId=ACCOUNT_ID, Namespace="default")
resp.should.have.key("UserList").equals([])
resp.should.have.key("Status").equals(200)
@mock_quicksight
def test_list_users():
client = boto3.client("quicksight", region_name="us-east-2")
for i in range(4):
client.register_user(
AwsAccountId=ACCOUNT_ID,
Namespace="default",
Email=f"fakeemail{i}@example.com",
IdentityType="QUICKSIGHT",
UserName=f"fake{i}",
UserRole="READER",
)
resp = client.list_users(AwsAccountId=ACCOUNT_ID, Namespace="default")
resp.should.have.key("UserList").length_of(4)
resp.should.have.key("Status").equals(200)
resp["UserList"].should.contain(
{
"Arn": f"arn:aws:quicksight:us-east-2:{ACCOUNT_ID}:user/default/fake0",
"UserName": "fake0",
"Email": "fakeemail0@example.com",
"Role": "READER",
"IdentityType": "QUICKSIGHT",
"Active": False,
}
)
resp["UserList"].should.contain(
{
"Arn": f"arn:aws:quicksight:us-east-2:{ACCOUNT_ID}:user/default/fake3",
"UserName": "fake3",
"Email": "fakeemail3@example.com",
"Role": "READER",
"IdentityType": "QUICKSIGHT",
"Active": False,
}
)
@mock_quicksight
def test_create_group_membership():
client = boto3.client("quicksight", region_name="us-east-2")
client.register_user(
AwsAccountId=ACCOUNT_ID,
Namespace="default",
Email=f"fakeemail@example.com",
IdentityType="QUICKSIGHT",
UserName="user1",
UserRole="READER",
)
client.create_group(
AwsAccountId=ACCOUNT_ID, Namespace="default", GroupName="group1"
)
resp = client.create_group_membership(
MemberName="user1",
GroupName="group1",
AwsAccountId=ACCOUNT_ID,
Namespace="default",
)
resp.should.have.key("GroupMember").equals(
{
"Arn": f"arn:aws:quicksight:us-east-2:{ACCOUNT_ID}:group/default/group1/user1",
"MemberName": "user1",
}
)
resp.should.have.key("Status").equals(200)
@mock_quicksight
def test_describe_group_membership():
client = boto3.client("quicksight", region_name="us-east-2")
client.register_user(
AwsAccountId=ACCOUNT_ID,
Namespace="default",
Email=f"fakeemail@example.com",
IdentityType="QUICKSIGHT",
UserName="user1",
UserRole="READER",
)
client.create_group(
AwsAccountId=ACCOUNT_ID, Namespace="default", GroupName="group1"
)
client.create_group_membership(
MemberName="user1",
GroupName="group1",
AwsAccountId=ACCOUNT_ID,
Namespace="default",
)
resp = client.describe_group_membership(
MemberName="user1",
GroupName="group1",
AwsAccountId=ACCOUNT_ID,
Namespace="default",
)
resp.should.have.key("GroupMember").equals(
{
"Arn": f"arn:aws:quicksight:us-east-2:{ACCOUNT_ID}:group/default/group1/user1",
"MemberName": "user1",
}
)
resp.should.have.key("Status").equals(200)
@mock_quicksight
def test_list_group_memberships():
client = boto3.client("quicksight", region_name="us-east-2")
for i in range(3):
client.register_user(
AwsAccountId=ACCOUNT_ID,
Namespace="default",
Email=f"fakeemail@example.com",
IdentityType="QUICKSIGHT",
UserName=f"user{i}",
UserRole="READER",
)
client.create_group(
AwsAccountId=ACCOUNT_ID, Namespace="default", GroupName="group1"
)
client.create_group(
AwsAccountId=ACCOUNT_ID, Namespace="default", GroupName="group2"
)
client.create_group_membership(
MemberName="user0",
GroupName="group1",
AwsAccountId=ACCOUNT_ID,
Namespace="default",
)
client.create_group_membership(
MemberName="user1",
GroupName="group1",
AwsAccountId=ACCOUNT_ID,
Namespace="default",
)
client.create_group_membership(
MemberName="user2",
GroupName="group2",
AwsAccountId=ACCOUNT_ID,
Namespace="default",
)
resp = client.list_group_memberships(
GroupName="group1", AwsAccountId=ACCOUNT_ID, Namespace="default"
)
resp.should.have.key("GroupMemberList").length_of(2)
resp.should.have.key("Status").equals(200)
resp["GroupMemberList"].should.contain(
{
"Arn": f"arn:aws:quicksight:us-east-2:{ACCOUNT_ID}:group/default/group1/user0",
"MemberName": "user0",
}
)
resp["GroupMemberList"].should.contain(
{
"Arn": f"arn:aws:quicksight:us-east-2:{ACCOUNT_ID}:group/default/group1/user1",
"MemberName": "user1",
}
)
@mock_quicksight
def test_list_group_memberships__after_deleting_user():
client = boto3.client("quicksight", region_name="us-east-2")
client.create_group(
AwsAccountId=ACCOUNT_ID, Namespace="default", GroupName="group1"
)
for i in range(3):
client.register_user(
AwsAccountId=ACCOUNT_ID,
Namespace="default",
Email=f"fakeemail@example.com",
IdentityType="QUICKSIGHT",
UserName=f"user{i}",
UserRole="READER",
)
client.create_group_membership(
MemberName=f"user{i}",
GroupName="group1",
AwsAccountId=ACCOUNT_ID,
Namespace="default",
)
resp = client.list_group_memberships(
GroupName="group1", AwsAccountId=ACCOUNT_ID, Namespace="default"
)
resp.should.have.key("GroupMemberList").length_of(3)
client.delete_user(UserName="user1", AwsAccountId=ACCOUNT_ID, Namespace="default")
resp = client.list_group_memberships(
GroupName="group1", AwsAccountId=ACCOUNT_ID, Namespace="default"
)
resp.should.have.key("GroupMemberList").length_of(2)