Skip to content

Commit 4560428

Browse files
author
Kenny Woodson
authored
[ARO] CLI enhancements, including route table checking permissions (Azure#14535)
* [ARO] CLI enhancements, including route table checking permissions * Adding the new recorded tests.
1 parent 875a6d9 commit 4560428

13 files changed

+7785
-7185
lines changed

src/azure-cli/azure/cli/command_modules/aro/_aad.py

+17-3
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,19 @@
44
# --------------------------------------------------------------------------------------------
55

66
import datetime
7+
import time
78
import uuid
89

910
from azure.cli.core._profile import Profile
1011
from azure.cli.core.commands.client_factory import configure_common_settings
1112
from azure.graphrbac import GraphRbacManagementClient
1213
from azure.graphrbac.models import ApplicationCreateParameters
14+
from azure.graphrbac.models import GraphErrorException
1315
from azure.graphrbac.models import PasswordCredential
1416
from azure.graphrbac.models import ServicePrincipalCreateParameters
17+
from knack.log import get_logger
18+
19+
logger = get_logger(__name__)
1520

1621

1722
class AADManager:
@@ -56,6 +61,15 @@ def get_service_principal(self, app_id):
5661
return None
5762

5863
def create_service_principal(self, app_id):
59-
return self.client.service_principals.create(ServicePrincipalCreateParameters(
60-
app_id=app_id,
61-
))
64+
max_retries = 3
65+
retries = 0
66+
while True:
67+
try:
68+
return self.client.service_principals.create(
69+
ServicePrincipalCreateParameters(app_id=app_id))
70+
except GraphErrorException as ex:
71+
if retries >= max_retries:
72+
raise
73+
retries += 1
74+
logger.warning("%s; retry %d of %d", ex, retries, max_retries)
75+
time.sleep(10)

src/azure-cli/azure/cli/command_modules/aro/_help.py

+41-41
Original file line numberDiff line numberDiff line change
@@ -7,66 +7,66 @@
77

88

99
helps['aro'] = """
10-
type: group
11-
short-summary: Manage Azure Red Hat OpenShift clusters.
10+
type: group
11+
short-summary: Manage Azure Red Hat OpenShift clusters.
1212
"""
1313

1414
helps['aro create'] = """
15-
type: command
16-
short-summary: Create a cluster.
17-
examples:
18-
- name: Create an OpenShift cluster
19-
text: az aro create --resource-group MyResourceGroup --name MyCluster --vnet MyVnet --master-subnet MyMasterSubnet --worker-subnet MyWorkerSubnet
20-
- name: Create an OpenShift cluster with 5 compute nodes and Red Hat Pull Secret
21-
text: az aro create --resource-group MyResourceGroup --name MyCluster --vnet MyVnet --master-subnet MyMasterSubnet --worker-subnet MyWorkerSubnet --worker-count 5 --pull-secret @pullsecret.txt
22-
- name: Create a Private OpenShift cluster
23-
text: az aro create --resource-group MyResourceGroup --name MyCluster --vnet MyVnet --master-subnet MyMasterSubnet --worker-subnet MyWorkerSubnet --apiserver-visibility Private --ingress-visibility Private
15+
type: command
16+
short-summary: Create a cluster.
17+
examples:
18+
- name: Create a cluster.
19+
text: az aro create --resource-group MyResourceGroup --name MyCluster --vnet MyVnet --master-subnet MyMasterSubnet --worker-subnet MyWorkerSubnet
20+
- name: Create a cluster with 5 compute nodes and Red Hat pull secret.
21+
text: az aro create --resource-group MyResourceGroup --name MyCluster --vnet MyVnet --master-subnet MyMasterSubnet --worker-subnet MyWorkerSubnet --worker-count 5 --pull-secret @pullsecret.txt
22+
- name: Create a Private cluster
23+
text: az aro create --resource-group MyResourceGroup --name MyCluster --vnet MyVnet --master-subnet MyMasterSubnet --worker-subnet MyWorkerSubnet --apiserver-visibility Private --ingress-visibility Private
2424
"""
2525

2626
helps['aro list'] = """
27-
type: command
28-
short-summary: List clusters.
29-
examples:
30-
- name: List OpenShift clusters
31-
text: az aro list
32-
- name: List OpenShift clusters with table view
33-
text: az aro list -o table
27+
type: command
28+
short-summary: List clusters.
29+
examples:
30+
- name: List clusters.
31+
text: az aro list
32+
- name: List clusters with table view.
33+
text: az aro list -o table
3434
"""
3535

3636
helps['aro delete'] = """
37-
type: command
38-
short-summary: Delete a cluster.
39-
examples:
40-
- name: Delete a OpenShift cluster
41-
text: az aro delete --name MyCluster --resource-group MyResourceGroup
37+
type: command
38+
short-summary: Delete a cluster.
39+
examples:
40+
- name: Delete a cluster.
41+
text: az aro delete --name MyCluster --resource-group MyResourceGroup
4242
"""
4343

4444
helps['aro show'] = """
45-
type: command
46-
short-summary: Get the details of a cluster.
47-
examples:
48-
- name: Get the details of a Managed OpenShift cluster
49-
text: az aro show --name MyCluster --resource-group MyResourceGroup
45+
type: command
46+
short-summary: Get the details of a cluster.
47+
examples:
48+
- name: Get the details of a cluster.
49+
text: az aro show --name MyCluster --resource-group MyResourceGroup
5050
"""
5151

5252
helps['aro update'] = """
53-
type: command
54-
short-summary: Update a cluster.
55-
examples:
56-
- name: Update an existing cluster
57-
text: az aro update --name MyCluster --resource-group MyResourceGroup
53+
type: command
54+
short-summary: Update a cluster.
55+
examples:
56+
- name: Update a cluster.
57+
text: az aro update --name MyCluster --resource-group MyResourceGroup
5858
"""
5959

6060
helps['aro list-credentials'] = """
61-
type: command
62-
short-summary: List credentials of a cluster.
63-
examples:
64-
- name: List credentials of a cluster
65-
text: az aro list-credentials --name MyCluster --resource-group MyResourceGroup
61+
type: command
62+
short-summary: List credentials of a cluster.
63+
examples:
64+
- name: List credentials of a cluster.
65+
text: az aro list-credentials --name MyCluster --resource-group MyResourceGroup
6666
"""
6767

6868
helps['aro wait'] = """
69-
type: command
70-
short-summary: Wait for a cluster to reach a desired state.
71-
long-summary: If an operation on a cluster was interrupted or was started with `--no-wait`, use this command to wait for it to complete.
69+
type: command
70+
short-summary: Wait for a cluster to reach a desired state.
71+
long-summary: If an operation on a cluster was interrupted or was started with `--no-wait`, use this command to wait for it to complete.
7272
"""

src/azure-cli/azure/cli/command_modules/aro/_params.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,13 @@
1818
from azure.cli.core.commands.parameters import name_type
1919
from azure.cli.core.commands.parameters import get_enum_type
2020
from azure.cli.core.commands.parameters import resource_group_name_type
21-
from azure.cli.core.commands.parameters import get_location_type
2221
from azure.cli.core.commands.parameters import tags_type
2322
from azure.cli.core.commands.validators import get_default_location_from_resource_group
2423

2524

2625
def load_arguments(self, _):
2726
with self.argument_context('aro') as c:
2827
c.argument('location',
29-
get_location_type(self.cli_ctx),
3028
validator=get_default_location_from_resource_group)
3129
c.argument('resource_name',
3230
name_type,
@@ -64,9 +62,11 @@ def load_arguments(self, _):
6462
c.argument('worker_vm_size',
6563
help='Size of worker VMs.')
6664
c.argument('worker_vm_disk_size_gb',
65+
type=int,
6766
help='Disk size in GB of worker VMs.',
6867
validator=validate_worker_vm_disk_size_gb)
6968
c.argument('worker_count',
69+
type=int,
7070
help='Count of worker VMs.',
7171
validator=validate_worker_count)
7272

src/azure-cli/azure/cli/command_modules/aro/_rbac.py

+55-6
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from azure.cli.core.profiles import get_sdk
1111
from azure.cli.core.profiles import ResourceType
1212
from msrestazure.tools import resource_id
13+
from msrestazure.tools import parse_resource_id
1314

1415

1516
CONTRIBUTOR = 'b24988ac-6180-42a0-ab88-20f7382dd24c'
@@ -20,7 +21,7 @@ def _gen_uuid():
2021

2122

2223
def assign_contributor_to_vnet(cli_ctx, vnet, object_id):
23-
client = get_mgmt_service_client(cli_ctx, ResourceType.MGMT_AUTHORIZATION)
24+
auth_client = get_mgmt_service_client(cli_ctx, ResourceType.MGMT_AUTHORIZATION)
2425

2526
RoleAssignmentCreateParameters = get_sdk(cli_ctx, ResourceType.MGMT_AUTHORIZATION,
2627
'RoleAssignmentCreateParameters', mod='models',
@@ -33,15 +34,63 @@ def assign_contributor_to_vnet(cli_ctx, vnet, object_id):
3334
name=CONTRIBUTOR,
3435
)
3536

36-
for assignment in list(client.role_assignments.list_for_scope(vnet)):
37-
if assignment.role_definition_id.lower() == role_definition_id.lower() and \
38-
assignment.principal_id.lower() == object_id.lower():
39-
return
37+
if has_assignment(auth_client.role_assignments.list_for_scope(vnet), role_definition_id, object_id):
38+
return
4039

40+
# generate random uuid for role assignment
4141
role_uuid = _gen_uuid()
4242

43-
client.role_assignments.create(vnet, role_uuid, RoleAssignmentCreateParameters(
43+
auth_client.role_assignments.create(vnet, role_uuid, RoleAssignmentCreateParameters(
4444
role_definition_id=role_definition_id,
4545
principal_id=object_id,
4646
principal_type='ServicePrincipal',
4747
))
48+
49+
50+
def assign_contributor_to_routetable(cli_ctx, master_subnet, worker_subnet, object_id):
51+
auth_client = get_mgmt_service_client(cli_ctx, ResourceType.MGMT_AUTHORIZATION)
52+
network_client = get_mgmt_service_client(cli_ctx, ResourceType.MGMT_NETWORK)
53+
54+
RoleAssignmentCreateParameters = get_sdk(cli_ctx, ResourceType.MGMT_AUTHORIZATION,
55+
'RoleAssignmentCreateParameters', mod='models',
56+
operation_group='role_assignments')
57+
58+
role_definition_id = resource_id(
59+
subscription=get_subscription_id(cli_ctx),
60+
namespace='Microsoft.Authorization',
61+
type='roleDefinitions',
62+
name=CONTRIBUTOR,
63+
)
64+
65+
route_tables = set()
66+
for sn in [master_subnet, worker_subnet]:
67+
sid = parse_resource_id(sn)
68+
69+
subnet = network_client.subnets.get(resource_group_name=sid['resource_group'],
70+
virtual_network_name=sid['name'],
71+
subnet_name=sid['resource_name'])
72+
73+
if subnet.route_table is not None:
74+
route_tables.add(subnet.route_table.id)
75+
76+
for rt in route_tables:
77+
if has_assignment(auth_client.role_assignments.list_for_scope(rt),
78+
role_definition_id, object_id):
79+
continue
80+
81+
role_uuid = _gen_uuid()
82+
83+
auth_client.role_assignments.create(rt, role_uuid, RoleAssignmentCreateParameters(
84+
role_definition_id=role_definition_id,
85+
principal_id=object_id,
86+
principal_type='ServicePrincipal',
87+
))
88+
89+
90+
def has_assignment(assignments, role_definition_id, object_id):
91+
for assignment in assignments:
92+
if assignment.role_definition_id.lower() == role_definition_id.lower() and \
93+
assignment.principal_id.lower() == object_id.lower():
94+
return True
95+
96+
return False

src/azure-cli/azure/cli/command_modules/aro/_validators.py

-15
Original file line numberDiff line numberDiff line change
@@ -72,15 +72,6 @@ def validate_domain(namespace):
7272
namespace.domain)
7373

7474

75-
def _validate_int(key, i):
76-
try:
77-
i = int(i)
78-
except ValueError:
79-
raise CLIError("Invalid --%s '%s'." % (key.replace('_', '-'), i))
80-
81-
return i
82-
83-
8475
def validate_pull_secret(namespace):
8576
if namespace.pull_secret is None:
8677
# TODO: add aka.ms link here
@@ -206,19 +197,13 @@ def validate_vnet_resource_group_name(namespace):
206197

207198
def validate_worker_count(namespace):
208199
if namespace.worker_count:
209-
namespace.worker_count = _validate_int(
210-
'worker_count', namespace.worker_count)
211-
212200
if namespace.worker_count < 3:
213201
raise CLIError(
214202
'--worker-count must be greater than or equal to 3.')
215203

216204

217205
def validate_worker_vm_disk_size_gb(namespace):
218206
if namespace.worker_vm_disk_size_gb:
219-
namespace.worker_vm_disk_size_gb = _validate_int(
220-
'worker_vm_disk_size_gb', namespace.worker_vm_disk_size_gb)
221-
222207
if namespace.worker_vm_disk_size_gb < 128:
223208
raise CLIError(
224209
'--worker-vm-disk-size-gb must be greater than or equal to 128.')

src/azure-cli/azure/cli/command_modules/aro/custom.py

+28-6
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@
88

99
import azure.mgmt.redhatopenshift.models as v2020_04_30
1010
from azure.cli.command_modules.aro._aad import AADManager
11-
from azure.cli.command_modules.aro._rbac import assign_contributor_to_vnet
11+
from azure.cli.command_modules.aro._rbac import assign_contributor_to_vnet, assign_contributor_to_routetable
1212
from azure.cli.command_modules.aro._validators import validate_subnets
13+
from azure.cli.core.commands.client_factory import get_mgmt_service_client
1314
from azure.cli.core.commands.client_factory import get_subscription_id
15+
from azure.cli.core.profiles import ResourceType
1416
from azure.cli.core.util import sdk_no_wait
17+
from knack.util import CLIError
1518

1619

1720
FP_CLIENT_ID = 'f1dd0a37-89c6-4e07-bcd1-ffd3d43d8875'
@@ -41,6 +44,13 @@ def aro_create(cmd, # pylint: disable=too-many-locals
4144
ingress_visibility=None,
4245
tags=None,
4346
no_wait=False):
47+
resource_client = get_mgmt_service_client(
48+
cmd.cli_ctx, ResourceType.MGMT_RESOURCE_RESOURCES)
49+
provider = resource_client.providers.get('Microsoft.RedHatOpenShift')
50+
if provider.registration_state != 'Registered':
51+
raise CLIError('Microsoft.RedHatOpenShift provider is not registered. Run `az provider ' +
52+
'register -n Microsoft.RedHatOpenShift --wait`.')
53+
4454
vnet = validate_subnets(master_subnet, worker_subnet)
4555

4656
subscription_id = get_subscription_id(cmd.cli_ctx)
@@ -49,7 +59,7 @@ def aro_create(cmd, # pylint: disable=too-many-locals
4959

5060
aad = AADManager(cmd.cli_ctx)
5161
if client_id is None:
52-
app, client_secret = aad.create_application('aro-%s' % random_id)
62+
app, client_secret = aad.create_application(cluster_resource_group or 'aro-' + random_id)
5363
client_id = app.app_id
5464

5565
client_sp = aad.get_service_principal(client_id)
@@ -60,8 +70,20 @@ def aro_create(cmd, # pylint: disable=too-many-locals
6070

6171
rp_client_sp = aad.get_service_principal(rp_client_id)
6272

63-
assign_contributor_to_vnet(cmd.cli_ctx, vnet, client_sp.object_id)
64-
assign_contributor_to_vnet(cmd.cli_ctx, vnet, rp_client_sp.object_id)
73+
for sp_id in [client_sp.object_id, rp_client_sp.object_id]:
74+
assign_contributor_to_vnet(cmd.cli_ctx, vnet, sp_id)
75+
assign_contributor_to_routetable(cmd.cli_ctx, master_subnet, worker_subnet, sp_id)
76+
77+
if rp_mode_development():
78+
worker_vm_size = worker_vm_size or 'Standard_D2s_v3'
79+
else:
80+
worker_vm_size = worker_vm_size or 'Standard_D4s_v3'
81+
82+
if apiserver_visibility is not None:
83+
apiserver_visibility = apiserver_visibility.capitalize()
84+
85+
if ingress_visibility is not None:
86+
ingress_visibility = ingress_visibility.capitalize()
6587

6688
oc = v2020_04_30.OpenShiftCluster(
6789
location=location,
@@ -87,7 +109,7 @@ def aro_create(cmd, # pylint: disable=too-many-locals
87109
worker_profiles=[
88110
v2020_04_30.WorkerProfile(
89111
name='worker', # TODO: 'worker' should not be hard-coded
90-
vm_size=worker_vm_size or 'Standard_D4s_v3',
112+
vm_size=worker_vm_size,
91113
disk_size_gb=worker_vm_disk_size_gb or 128,
92114
subnet_id=worker_subnet,
93115
count=worker_count or 3,
@@ -146,7 +168,7 @@ def rp_mode_development():
146168

147169

148170
def generate_random_id():
149-
random_id = (''.join(random.choice('abcdefghijklmnopqrstuvwxyz')) +
171+
random_id = (random.choice('abcdefghijklmnopqrstuvwxyz') +
150172
''.join(random.choice('abcdefghijklmnopqrstuvwxyz1234567890')
151173
for _ in range(7)))
152174
return random_id

0 commit comments

Comments
 (0)