@@ -56,7 +56,7 @@ Parameters:
5656 Description : ARN of a Lambda for Managing GlueTable
5757 AwsObjects :
5858 Type : CommaDelimitedList
59- Default : OpensearchDomains, ElasticacheClusters, RdsDbInstances, EBS, AMI, Snapshot, Ec2Instances, VpcInstances, RdsDbSnapshots, EKSClusters, LambdaFunctions, RdsDbClusters
59+ Default : OpensearchDomains, ElasticacheClusters, RdsDbInstances, EBS, AMI, Snapshot, Ec2Instances, VpcInstances, RdsDbSnapshots, EKSClusters, LambdaFunctions, RdsDbClusters, WorkSpaces
6060 Description : Services for pulling price data
6161 DataBucketsKmsKeysArns :
6262 Type : String
@@ -1011,6 +1011,73 @@ Mappings:
10111011 SerializationLibrary : org.openx.data.jsonserde.JsonSerDe
10121012 TableType : EXTERNAL_TABLE
10131013
1014+ WorkSpaces :
1015+ path : workspaces
1016+ table :
1017+ - Name : inventory_workspaces_data
1018+ Parameters :
1019+ classification : json
1020+ compressionType : none
1021+ PartitionKeys :
1022+ - Name : payer_id
1023+ Type : string
1024+ - Name : year
1025+ Type : string
1026+ - Name : month
1027+ Type : string
1028+ - Name : day
1029+ Type : string
1030+ StorageDescriptor :
1031+ Columns :
1032+ - Name : workspaceid
1033+ Type : string
1034+ - Name : username
1035+ Type : string
1036+ - Name : computetype
1037+ Type : string
1038+ - Name : directoryid
1039+ Type : string
1040+ - Name : directoryname
1041+ Type : string
1042+ - Name : directorytype
1043+ Type : string
1044+ - Name : directoryalias
1045+ Type : string
1046+ - Name : directorymaintenance
1047+ Type : string
1048+ - Name : state
1049+ Type : string
1050+ - Name : connectionstatus
1051+ Type : string
1052+ - Name : lastconnected
1053+ Type : string
1054+ - Name : lastinventoryrun
1055+ Type : string
1056+ - Name : runningmode
1057+ Type : string
1058+ - Name : operatingsystemname
1059+ Type : string
1060+ - Name : protocol
1061+ Type : string
1062+ - Name : computername
1063+ Type : string
1064+ - Name : ipaddress
1065+ Type : string
1066+ - Name : accountid
1067+ Type : string
1068+ - Name : collection_date
1069+ Type : string
1070+ - Name : region
1071+ Type : string
1072+ InputFormat : org.apache.hadoop.mapred.TextInputFormat
1073+ Location : !Sub s3://${DestinationBucket}/inventory/inventory-workspaces-data/
1074+ OutputFormat : org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat
1075+ SerdeInfo :
1076+ Parameters :
1077+ paths : WorkspaceId,UserName,ComputeType,DirectoryId,DirectoryName,DirectoryType,DirectoryAlias,DirectoryMaintenance,State,ConnectionStatus,LastConnected,LastInventoryRun,RunningMode,OperatingSystemName,Protocol,ComputerName,IPAddress,accountid,collection_date,region
1078+ SerializationLibrary : org.openx.data.jsonserde.JsonSerDe
1079+ TableType : EXTERNAL_TABLE
1080+
10141081Resources :
10151082 LambdaRole :
10161083 Type : AWS::IAM::Role
@@ -1196,6 +1263,68 @@ Resources:
11961263 logger.error(f"Cannot get info from {account_id}/{region}: {type(exc)}-{exc}")
11971264 return []
11981265
1266+ def workspaces_scan(account_id, region):
1267+ """Special function to scan AWS WorkSpaces resources"""
1268+ # Define WorkSpaces supported regions
1269+ WORKSPACES_REGIONS = [
1270+ 'us-east-1', 'us-west-2', 'ap-south-1', 'ap-northeast-2',
1271+ 'ap-southeast-1', 'ap-southeast-2', 'ap-northeast-1', 'ca-central-1',
1272+ 'eu-central-1', 'eu-west-1', 'eu-west-2', 'sa-east-1']
1273+ # Skip if region is not supported by WorkSpaces
1274+ if region not in WORKSPACES_REGIONS:
1275+ logger.info(f"WorkSpaces not supported in region {region}. Skipping.")
1276+ return
1277+ try:
1278+ session = assume_session(account_id, region)
1279+ client = session.client('workspaces', region_name=region)
1280+ # Get WorkSpaces data
1281+ workspaces_data = list(client.get_paginator('describe_workspaces').paginate().search('Workspaces[*]'))
1282+ if not workspaces_data:
1283+ logger.info(f"No WorkSpaces found in {account_id}/{region}")
1284+ return
1285+ # Get connection status and directories for lookup
1286+ connection_status = client.describe_workspaces_connection_status()['WorkspacesConnectionStatus']
1287+ directories = client.describe_workspace_directories()['Directories']
1288+ # Create lookup dictionaries
1289+ connection_lookup = {conn['WorkspaceId']: conn for conn in connection_status}
1290+ directory_lookup = {d['DirectoryId']: d for d in directories}
1291+ # Process workspaces
1292+ for workspace in workspaces_data:
1293+ workspace_id = workspace['WorkspaceId']
1294+ dir_id = workspace['DirectoryId']
1295+
1296+ connection_info = connection_lookup.get(workspace_id, {})
1297+ dir_info = directory_lookup.get(dir_id, {})
1298+
1299+ workspace_info = {
1300+ 'accountid': account_id,
1301+ 'region': region,
1302+ 'ResourceType': 'WorkSpace',
1303+ 'WorkspaceId': workspace_id,
1304+ 'UserName': workspace['UserName'],
1305+ 'DirectoryId': dir_id,
1306+ 'State': workspace['State'],
1307+ 'ConnectionStatus': connection_info.get('ConnectionState', 'N/A'),
1308+ 'LastConnected': connection_info.get('LastKnownUserConnectionTimestamp', 'Never').isoformat() if isinstance(connection_info.get('LastKnownUserConnectionTimestamp'), datetime) else "01/01/01 00:00:00",
1309+ 'LastInventoryRun': time.strftime('%x %X'),
1310+ 'RunningMode': workspace.get("WorkspaceProperties", {}).get("RunningMode", "N/A"),
1311+ 'ComputeType': workspace.get("WorkspaceProperties", {}).get("ComputeTypeName", "N/A"),
1312+ 'OperatingSystem': workspace.get("WorkspaceProperties", {}).get("OperatingSystemName", "N/A"),
1313+ 'Protocol': ''.join(
1314+ c for c in str(workspace.get("WorkspaceProperties", {}).get("Protocols", "N/A"))
1315+ if c.isalnum() or c.isspace()
1316+ ),
1317+ 'DirectoryName': dir_info.get('DirectoryName', 'N/A'),
1318+ 'DirectoryAlias': dir_info.get('Alias', 'N/A'),
1319+ 'DirectoryType': dir_info.get('DirectoryType', 'N/A'),
1320+ 'DirectoryMaintenance': dir_info.get("WorkspaceCreationProperties", {}).get("EnableMaintenanceMode", "N/A"),
1321+ 'ComputerName': workspace.get("ComputerName", ""),
1322+ 'IPAddress': workspace.get("IpAddress", "")
1323+ }
1324+ yield workspace_info
1325+ except Exception as exc:
1326+ logger.error(f"Cannot get WorkSpaces info from {account_id}/{region}: {type(exc)}-{exc}")
1327+
11991328 def lambda_handler(event, context): #pylint: disable=unused-argument
12001329 """ this lambda collects ami, snapshots and volumes from linked accounts
12011330 and must be called from the corresponding Step Function to orchestrate
@@ -1270,7 +1399,8 @@ Resources:
12701399 function_name='list_functions',
12711400 obj_name='Functions[*]'
12721401 ),
1273- 'eks': eks_clusters_scan
1402+ 'eks': eks_clusters_scan,
1403+ 'workspaces': workspaces_scan
12741404 }
12751405
12761406 account = json.loads(event["account"])
@@ -1799,6 +1929,37 @@ Resources:
17991929 gp3_gb_cost + gp3_iops_cost + gp3_throughput_cost AS gp3_total_cost,
18001930 (current_gb_cost + current_iops_cost) - (gp3_gb_cost + gp3_iops_cost + gp3_throughput_cost) as gp3_saving
18011931 FROM inventory_ebs_data
1932+
1933+ EUCInventoryView :
1934+ Type : AWS::Athena::NamedQuery
1935+ Properties :
1936+ Database : !Ref DatabaseName
1937+ Description : EUC Inventory View for WorkSpaces inventory data
1938+ Name : create_euc_inventory_view
1939+ QueryString : !Sub |
1940+ CREATE OR REPLACE VIEW "euc_inventory_view" AS
1941+ SELECT
1942+ "workspaceid"
1943+ , "username"
1944+ , "accountid"
1945+ , "computetype"
1946+ , "directoryid"
1947+ , "directoryname"
1948+ , "directoryalias"
1949+ , "directorytype"
1950+ , "directorymaintenance"
1951+ , "region"
1952+ , "state"
1953+ , "connectionstatus"
1954+ , CAST(parse_datetime(lastconnected, 'MM/dd/yy HH:mm:ss') AS timestamp) lastconnected
1955+ , "lastinventoryrun"
1956+ , "runningmode"
1957+ , "operatingsystemname"
1958+ , "protocol"
1959+ , "computername"
1960+ , "ipaddress"
1961+ FROM
1962+ "inventory_workspaces_data"
18021963
18031964 AthenaBackwardCompatPricingRegionNames :
18041965 Type : AWS::Athena::NamedQuery
0 commit comments