Skip to content

Commit

Permalink
Merge pull request Azure#110 from StrawnSC/microsoft.graph
Browse files Browse the repository at this point in the history
Use new Microsoft.Graph API
  • Loading branch information
StrawnSC authored May 22, 2022
2 parents 433433a + d4f6cec commit 2d39def
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 122 deletions.
6 changes: 3 additions & 3 deletions src/containerapp/azext_containerapp/_up_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
_get_default_containerapps_location,
safe_get,
is_int,
create_service_principal_for_rbac,
create_service_principal_for_github_action,
repo_url_to_name,
get_container_app_if_exists,
trigger_workflow,
Expand Down Expand Up @@ -350,9 +350,9 @@ def _create_service_principal(cmd, resource_group_name, env_resource_group_name)
scopes.append(
f"/subscriptions/{get_subscription_id(cmd.cli_ctx)}/resourceGroups/{env_resource_group_name}"
)
sp = create_service_principal_for_rbac(cmd, scopes=scopes, role="contributor")
sp = create_service_principal_for_github_action(cmd, scopes=scopes, role="contributor")

logger.warning(f"Created service principal: {sp['displayName']} with ID {sp['appId']}")
logger.warning(f"Created service principal with ID {sp['appId']}")

return sp["appId"], sp["password"], sp["tenant"]

Expand Down
157 changes: 40 additions & 117 deletions src/containerapp/azext_containerapp/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,17 @@ def validate_container_app_name(name):
f"Please shorten {name}")


def retry_until_success(operation, err_txt, retry_limit, *args, **kwargs):
try:
return operation(*args, **kwargs)
except Exception as e:
retry_limit -= 1
if retry_limit <= 0:
raise CLIInternalError(err_txt) from e
time.sleep(5)
logger.info(f"Encountered error: {e}. Retrying...")


def get_vnet_location(cmd, subnet_resource_id):
parsed_rid = parse_resource_id(subnet_resource_id)
vnet_client = network_client_factory(cmd.cli_ctx)
Expand All @@ -48,126 +59,38 @@ def get_vnet_location(cmd, subnet_resource_id):
return _normalize_location(cmd, location)


# original implementation at azure.cli.command_modules.role.custom.create_service_principal_for_rbac
# reimplemented to remove incorrect warning statements
def create_service_principal_for_rbac( # pylint:disable=too-many-statements,too-many-locals, too-many-branches, unused-argument, inconsistent-return-statements
cmd, name=None, years=None, create_cert=False, cert=None, scopes=None, role=None,
show_auth_for_sdk=None, skip_assignment=False, keyvault=None):
from azure.cli.command_modules.role.custom import (_graph_client_factory, TZ_UTC, _process_service_principal_creds,
_validate_app_dates, create_application,
_create_service_principal, _create_role_assignment,
_error_caused_by_role_assignment_exists)

if role and not scopes or not role and scopes:
raise ArgumentUsageError("Usage error: To create role assignments, specify both --role and --scopes.")

graph_client = _graph_client_factory(cmd.cli_ctx)

years = years or 1
_RETRY_TIMES = 36
existing_sps = None
def create_service_principal_for_github_action(cmd, scopes=None, role="contributor"):
from azure.cli.command_modules.role.custom import (create_application, create_service_principal,
create_role_assignment, show_service_principal)
from azure.cli.command_modules.role.msgrpah._graph_client import GraphClient

SP_CREATION_ERR_TXT = "Failed to create service principal."
RETRY_LIMIT = 36

client = GraphClient(cmd.cli_ctx)
now = datetime.utcnow()
app_display_name = 'azure-cli-' + now.strftime('%Y-%m-%d-%H-%M-%S')
app = retry_until_success(create_application, SP_CREATION_ERR_TXT, RETRY_LIMIT, cmd, client, display_name=app_display_name)
sp = retry_until_success(create_service_principal, SP_CREATION_ERR_TXT, RETRY_LIMIT, cmd, identifier=app["appId"])
for scope in scopes:
retry_until_success(create_role_assignment, SP_CREATION_ERR_TXT, RETRY_LIMIT, cmd, role=role, assignee=sp["id"], scope=scope)
service_principal = retry_until_success(show_service_principal, SP_CREATION_ERR_TXT, RETRY_LIMIT, client, sp["id"])

body = {
"passwordCredential": {
"displayName": None,
"startDateTime": now.strftime('%Y-%m-%dT%H:%M:%SZ'),
"endDateTime": (now + relativedelta(years=1)).strftime('%Y-%m-%dT%H:%M:%SZ'),
}
}

if not name:
# No name is provided, create a new one
app_display_name = 'azure-cli-' + datetime.utcnow().strftime('%Y-%m-%d-%H-%M-%S')
else:
app_display_name = name
# patch existing app with the same displayName to make the command idempotent
query_exp = "displayName eq '{}'".format(name)
existing_sps = list(graph_client.service_principals.list(filter=query_exp))

app_start_date = datetime.now(TZ_UTC)
app_end_date = app_start_date + relativedelta(years=years or 1)

password, public_cert_string, cert_file, cert_start_date, cert_end_date = \
_process_service_principal_creds(cmd.cli_ctx, years, app_start_date, app_end_date, cert, create_cert,
None, keyvault)

app_start_date, app_end_date, cert_start_date, cert_end_date = \
_validate_app_dates(app_start_date, app_end_date, cert_start_date, cert_end_date)

aad_application = create_application(cmd,
display_name=app_display_name,
available_to_other_tenants=False,
password=password,
key_value=public_cert_string,
start_date=app_start_date,
end_date=app_end_date,
credential_description='rbac')
# pylint: disable=no-member
app_id = aad_application.app_id

# retry till server replication is done
aad_sp = existing_sps[0] if existing_sps else None
if not aad_sp:
for retry_time in range(0, _RETRY_TIMES):
try:
aad_sp = _create_service_principal(cmd.cli_ctx, app_id, resolve_app=False)
break
except Exception as ex: # pylint: disable=broad-except
err_msg = str(ex)
if retry_time < _RETRY_TIMES and (
' does not reference ' in err_msg or
' does not exist ' in err_msg or
'service principal being created must in the local tenant' in err_msg):
logger.warning("Creating service principal failed with error '%s'. Retrying: %s/%s",
err_msg, retry_time + 1, _RETRY_TIMES)
time.sleep(5)
else:
logger.warning(
"Creating service principal failed for '%s'. Trace followed:\n%s",
app_id, ex.response.headers
if hasattr(ex, 'response') else ex) # pylint: disable=no-member
raise
sp_oid = aad_sp.object_id

if role:
for scope in scopes:
# logger.warning("Creating '%s' role assignment under scope '%s'", role, scope)
# retry till server replication is done
for retry_time in range(0, _RETRY_TIMES):
try:
_create_role_assignment(cmd.cli_ctx, role, sp_oid, None, scope, resolve_assignee=False,
assignee_principal_type='ServicePrincipal')
break
except Exception as ex:
if retry_time < _RETRY_TIMES and ' does not exist in the directory ' in str(ex):
time.sleep(5)
logger.warning(' Retrying role assignment creation: %s/%s', retry_time + 1,
_RETRY_TIMES)
continue
if _error_caused_by_role_assignment_exists(ex):
logger.warning(' Role assignment already exists.\n')
break

# dump out history for diagnoses
logger.warning(' Role assignment creation failed.\n')
if getattr(ex, 'response', None) is not None:
logger.warning(' role assignment response headers: %s\n',
ex.response.headers) # pylint: disable=no-member
raise

if show_auth_for_sdk:
from azure.cli.core._profile import Profile
profile = Profile(cli_ctx=cmd.cli_ctx)
result = profile.get_sp_auth_info(scopes[0].split('/')[2] if scopes else None,
app_id, password, cert_file)
# sdk-auth file should be in json format all the time, hence the print
print(json.dumps(result, indent=2))
return
add_password_result = retry_until_success(client.service_principal_add_password, SP_CREATION_ERR_TXT, RETRY_LIMIT, service_principal["id"], body)

result = {
'appId': app_id,
'password': password,
'displayName': app_display_name,
'tenant': graph_client.config.tenant_id
return {
'appId': service_principal['appId'],
'password': add_password_result['secretText'],
'tenant': client.tenant
}
if cert_file:
logger.warning(
"Please copy %s to a safe place. When you run 'az login', provide the file path in the --password argument",
cert_file)
result['fileWithCertAndPrivateKey'] = cert_file
return result


def is_int(s):
Expand Down
3 changes: 1 addition & 2 deletions src/containerapp/azext_containerapp/azext_metadata.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{
"azext.isPreview": true,
"azext.minCliCoreVersion": "2.15.0",
"azext.maxCliCoreVersion": "2.36.0"
"azext.minCliCoreVersion": "2.37.0"
}

0 comments on commit 2d39def

Please sign in to comment.