Skip to content

Commit 8c7bb41

Browse files
committed
@W-18129346 - Adding new tasks get_assignable_permission_sets and get_assignable_licenses
1 parent 769028b commit 8c7bb41

5 files changed

Lines changed: 104 additions & 11 deletions

File tree

.github/workflows/release_test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ jobs:
3434
pip uninstall -y cumulusci
3535
- name: Store artifacts
3636
if: failure()
37-
uses: actions/upload-artifact@v3
37+
uses: actions/upload-artifact@v4
3838
with:
3939
name: packages
4040
path: dist

.github/workflows/release_test_sfdx.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ jobs:
4747
python-version: 3.11
4848
cache: pip
4949
cache-dependency-path: "pyproject.toml"
50+
- name: Check pip cache existence
51+
run: |
52+
if [ ! -d ~/.cache/pip ]; then
53+
echo "No pip cache directory found, skipping cache step."
54+
fi
55+
5056
- name: Set up uv
5157
uses: SFDO-Tooling/setup-uv@main
5258
with:

cumulusci/cumulusci.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,10 @@ tasks:
317317
description: Retrieves a list of the currently available license definition keys
318318
class_path: cumulusci.tasks.preflight.licenses.GetAvailableLicenses
319319
group: Salesforce Preflight Checks
320+
get_assignable_licenses:
321+
description: Retrieves a list of the currently assignable license definition keys based on unused licenses
322+
class_path: cumulusci.tasks.preflight.licenses.GetAssignableLicenses
323+
group: Salesforce Preflight Checks
320324
get_available_permission_set_licenses:
321325
description: Retrieves a list of the currently available Permission Set License definition keys
322326
class_path: cumulusci.tasks.preflight.licenses.GetAvailablePermissionSetLicenses
@@ -333,6 +337,10 @@ tasks:
333337
description: Retrieves a list of the currently available Permission Sets
334338
class_path: cumulusci.tasks.preflight.licenses.GetAvailablePermissionSets
335339
group: Salesforce Preflight Checks
340+
get_assignable_permission_sets:
341+
description: Retrieves a list of the currently assignable Permission Sets based on unused associated user licenses
342+
class_path: cumulusci.tasks.preflight.licenses.GetAssignablePermissionSets
343+
group: Salesforce Preflight Checks
336344
get_existing_record_types:
337345
description: "Retrieves all Record Types in the org as a dict, with sObject names as keys and lists of Developer Names as values."
338346
class_path: cumulusci.tasks.preflight.recordtypes.CheckSObjectRecordTypes

cumulusci/tasks/preflight/licenses.py

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,40 @@
11
from cumulusci.tasks.salesforce import BaseSalesforceApiTask
22

33

4-
class GetAvailableLicenses(BaseSalesforceApiTask):
4+
class BaseUserLicenseAwareTask(BaseSalesforceApiTask):
5+
def get_available_user_licenses(self, is_assignable=False):
6+
"""Fetch active user licenses with availability."""
7+
query = "SELECT Id, LicenseDefinitionKey, TotalLicenses, UsedLicenses FROM UserLicense WHERE Status = 'Active'"
8+
return {
9+
lic["Id"]: lic
10+
for lic in self.sf.query(query)["records"]
11+
if not is_assignable or (lic["TotalLicenses"] > lic["UsedLicenses"])
12+
}
13+
14+
def _log_list(self, title, items):
15+
self.logger.info(f"{title} ({len(items)}):\n" + "\n".join(f"- {item}" for item in items))
16+
17+
18+
class GetAvailableLicenses(BaseUserLicenseAwareTask):
519
def _run_task(self):
620
self.return_values = [
721
result["LicenseDefinitionKey"]
8-
for result in self.sf.query("SELECT LicenseDefinitionKey FROM UserLicense")[
9-
"records"
10-
]
22+
for result in self.get_available_user_licenses().values()
1123
]
1224
licenses = "\n".join(self.return_values)
1325
self.logger.info(f"Found licenses:\n{licenses}")
1426

1527

28+
class GetAssignableLicenses(BaseUserLicenseAwareTask):
29+
def _run_task(self):
30+
self.return_values = [
31+
result["LicenseDefinitionKey"]
32+
for result in self.get_available_user_licenses(is_assignable=True).values()
33+
]
34+
licenses = "\n".join(self.return_values)
35+
self.logger.info(f"Found assignable licenses:\n{licenses}")
36+
37+
1638
class GetAvailablePermissionSetLicenses(BaseSalesforceApiTask):
1739
def _run_task(self):
1840
query = "SELECT PermissionSetLicenseKey FROM PermissionSetLicense WHERE Status = 'Active'"
@@ -43,3 +65,18 @@ def _run_task(self):
4365
]
4466
permsets = "\n".join(self.return_values)
4567
self.logger.info(f"Found Permission Sets:\n{permsets}")
68+
69+
70+
class GetAssignablePermissionSets(BaseUserLicenseAwareTask):
71+
def _run_task(self):
72+
license_data = self.get_available_user_licenses(is_assignable=True)
73+
permsets = self.sf.query_all("SELECT LicenseId, Name FROM PermissionSet")["records"]
74+
available_permsets = [
75+
ps["Name"]
76+
for ps in permsets
77+
if not ps["LicenseId"] or ps["LicenseId"] in license_data
78+
]
79+
80+
self.return_values = available_permsets
81+
permsets = "\n".join(self.return_values)
82+
self.logger.info(f"Found assignable permission sets:\n{permsets}")

cumulusci/tasks/preflight/tests/test_licenses.py

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
from unittest.mock import Mock
22

33
from cumulusci.tasks.preflight.licenses import (
4+
GetAssignableLicenses,
45
GetAvailableLicenses,
56
GetAvailablePermissionSetLicenses,
7+
GetAssignablePermissionSets,
68
GetAvailablePermissionSets,
79
GetPermissionLicenseSetAssignments,
810
)
@@ -12,20 +14,32 @@
1214
class TestLicensePreflights:
1315
def test_license_preflight(self):
1416
task = create_task(GetAvailableLicenses, {})
17+
task.get_available_user_licenses = Mock(return_value={
18+
"L1": {"LicenseDefinitionKey": "TEST1"},
19+
"L3": {"LicenseDefinitionKey": "TEST3"},
20+
})
21+
22+
task()
23+
# Only TEST1 and TEST3 have available licenses
24+
assert task.return_values == ["TEST1", "TEST3"]
25+
26+
def test_assignable_license_preflight(self):
27+
task = create_task(GetAssignableLicenses, {})
1528
task._init_api = Mock()
1629
task._init_api.return_value.query.return_value = {
1730
"totalSize": 2,
1831
"records": [
19-
{"LicenseDefinitionKey": "TEST1"},
20-
{"LicenseDefinitionKey": "TEST2"},
32+
{"Id": "L1", "LicenseDefinitionKey": "TEST1", "TotalLicenses": 100, "UsedLicenses": 90},
33+
{"Id": "L2", "LicenseDefinitionKey": "TEST2", "TotalLicenses": 100, "UsedLicenses": 100},
2134
],
2235
}
23-
task()
2436

37+
task()
2538
task._init_api.return_value.query.assert_called_once_with(
26-
"SELECT LicenseDefinitionKey FROM UserLicense"
39+
"SELECT Id, LicenseDefinitionKey, TotalLicenses, UsedLicenses FROM UserLicense WHERE Status = 'Active'"
2740
)
28-
assert task.return_values == ["TEST1", "TEST2"]
41+
# Only TEST1 and TEST3 have available licenses
42+
assert task.return_values == ["TEST1"]
2943

3044
def test_psl_preflight(self):
3145
task = create_task(GetAvailablePermissionSetLicenses, {})
@@ -61,7 +75,7 @@ def test_assigned_permsetlicense_preflight(self):
6175
{
6276
"PermissionSetLicense": {
6377
"MasterLabel": "Einstein Analytics Plus Admin",
64-
"DeveloperName": "EinsteinAnalyticsPlusAdmin",
78+
"DeveloperName": "EinsteinAnalyticsPlusAdmin"
6579
},
6680
},
6781
],
@@ -93,3 +107,31 @@ def test_permsets_preflight(self):
93107
"SELECT Name FROM PermissionSet"
94108
)
95109
assert task.return_values == ["TEST1", "TEST2"]
110+
111+
def test_assignable_permsets_preflight(self):
112+
task = create_task(GetAssignablePermissionSets, {})
113+
task._init_api = Mock()
114+
task._init_api.return_value.query.return_value = {
115+
"totalSize": 2,
116+
"records": [
117+
{"Id": "L1", "LicenseDefinitionKey": "TEST1", "TotalLicenses": 100, "UsedLicenses": 90},
118+
{"Id": "L2", "LicenseDefinitionKey": "TEST2", "TotalLicenses": 100, "UsedLicenses": 100},
119+
],
120+
}
121+
task._init_api.return_value.query_all.return_value = {
122+
"totalSize": 3,
123+
"records": [
124+
{"LicenseId": "L1", "Name": "TEST1"},
125+
{"LicenseId": "L2", "Name": "TEST2"},
126+
{"LicenseId": None, "Name": "TEST3"},
127+
],
128+
}
129+
task()
130+
131+
task._init_api.return_value.query.assert_called_once_with(
132+
"SELECT Id, LicenseDefinitionKey, TotalLicenses, UsedLicenses FROM UserLicense WHERE Status = 'Active'"
133+
)
134+
task._init_api.return_value.query_all.assert_called_once_with(
135+
"SELECT LicenseId, Name FROM PermissionSet"
136+
)
137+
assert task.return_values == ["TEST1", "TEST3"]

0 commit comments

Comments
 (0)