Skip to content

Commit 6da2c72

Browse files
authored
containerapp Version 0.3.4 Release (#4794)
1 parent c0443c9 commit 6da2c72

30 files changed

+40218
-104
lines changed

scripts/ci/credscan/CredScanSuppressions.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,25 @@
124124
"src\\aks-preview\\azext_aks_preview\\tests\\latest\\data\\setup_proxy.sh"
125125
],
126126
"_justification": "Dummy self-signed certificate + private key used for testing only."
127+
},
128+
{
129+
"file": [
130+
"src\\containerapp\\azext_containerapp\\tests\\latest\\recordings\\test_container_acr.yaml",
131+
"src\\containerapp\\azext_containerapp\\tests\\latest\\recordings\\test_containerapp_e2e.yaml",
132+
"src\\containerapp\\azext_containerapp\\tests\\latest\\recordings\\test_containerapp_env_dapr_components.yaml",
133+
"src\\containerapp\\azext_containerapp\\tests\\latest\\recordings\\test_containerapp_env_e2e.yaml",
134+
"src\\containerapp\\azext_containerapp\\tests\\latest\\recordings\\test_containerapp_env_storage.yaml",
135+
"src\\containerapp\\azext_containerapp\\tests\\latest\\recordings\\test_containerapp_identity_e2e.yaml",
136+
"src\\containerapp\\azext_containerapp\\tests\\latest\\recordings\\test_containerapp_identity_system.yaml",
137+
"src\\containerapp\\azext_containerapp\\tests\\latest\\recordings\\test_containerapp_identity_user.yaml",
138+
"src\\containerapp\\azext_containerapp\\tests\\latest\\recordings\\test_containerapp_ingress_e2e.yaml",
139+
"src\\containerapp\\azext_containerapp\\tests\\latest\\recordings\\test_containerapp_ingress_traffic_e2e.yaml",
140+
"src\\containerapp\\azext_containerapp\\tests\\latest\\recordings\\test_containerapp_logstream.yaml",
141+
"src\\containerapp\\azext_containerapp\\tests\\latest\\recordings\\test_containerapp_update.yaml",
142+
"src\\containerapp\\azext_containerapp\\tests\\latest\\recordings\\test_containerapp_dapr_e2e.yaml",
143+
"src\\containerapp\\azext_containerapp\\tests\\latest\\recordings\\test_containerapp_up_image_e2e.yaml"
144+
],
145+
"_justification": "Dummy resources' keys left during testing Microsoft.App (required for log-analytics to create managedEnvironments)"
127146
}
128147
]
129148
}

src/containerapp/HISTORY.rst

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,16 @@
33
Release History
44
===============
55

6+
0.3.4
7+
++++++
8+
* BREAKING CHANGE: 'az containerapp up' and 'az containerapp github-action add' now use the github repo's default branch instead of "main"
9+
* 'az containerapp up' now caches Github credentials so the user won't be prompted to sign in if using the same repo
10+
* Fixed bug with 'az containerapp up --repo' where it hangs after creating github action
11+
* Added 'az containerapp env storage' to manage Container App environment file shares
12+
613
0.3.3
714
++++++
815
* Improved 'az containerapp up' handling of environment locations
9-
* Added 'az containerapp env storage' to manage Container App environment file shares
1016

1117
0.3.2
1218
++++++

src/containerapp/azext_containerapp/_clients.py

Lines changed: 138 additions & 22 deletions
Large diffs are not rendered by default.

src/containerapp/azext_containerapp/_github_oauth.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,16 @@
44
# --------------------------------------------------------------------------------------------
55
# pylint: disable=consider-using-f-string
66

7+
import os
8+
import sys
9+
from datetime import datetime
10+
11+
from knack.log import get_logger
712
from azure.cli.core.util import open_page_in_browser
13+
from azure.cli.core.auth.persistence import SecretStore, build_persistence
814
from azure.cli.core.azclierror import (ValidationError, CLIInternalError, UnclassifiedUserFault)
9-
from knack.log import get_logger
15+
16+
from ._utils import repo_url_to_name
1017

1118
logger = get_logger(__name__)
1219

@@ -25,6 +32,45 @@
2532
]
2633

2734

35+
def _get_github_token_secret_store(cmd):
36+
location = os.path.join(cmd.cli_ctx.config.config_dir, "github_token_cache")
37+
# TODO use core CLI util to take care of this once it's merged and released
38+
encrypt = sys.platform.startswith('win32') # encryption not supported on non-windows platforms
39+
file_persistence = build_persistence(location, encrypt)
40+
return SecretStore(file_persistence)
41+
42+
43+
def cache_github_token(cmd, token, repo):
44+
repo = repo_url_to_name(repo)
45+
secret_store = _get_github_token_secret_store(cmd)
46+
cache = secret_store.load()
47+
48+
for entry in cache:
49+
if isinstance(entry, dict) and entry.get("value") == token:
50+
if repo not in entry.get("repos", []):
51+
entry["repos"] = [*entry.get("repos", []), repo]
52+
entry["last_modified_timestamp"] = datetime.utcnow().timestamp()
53+
break
54+
else:
55+
cache_entry = {"last_modified_timestamp": datetime.utcnow().timestamp(), "value": token, "repos": [repo]}
56+
cache = [cache_entry, *cache]
57+
58+
secret_store.save(cache)
59+
60+
61+
def load_github_token_from_cache(cmd, repo):
62+
repo = repo_url_to_name(repo)
63+
secret_store = _get_github_token_secret_store(cmd)
64+
cache = secret_store.load()
65+
66+
if isinstance(cache, list):
67+
for entry in cache:
68+
if isinstance(entry, dict) and repo in entry.get("repos", []):
69+
return entry.get("value")
70+
71+
return None
72+
73+
2874
def get_github_access_token(cmd, scope_list=None, token=None): # pylint: disable=unused-argument
2975
if token:
3076
return token

src/containerapp/azext_containerapp/_help.py

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,32 @@
263263
az containerapp revision copy -n MyContainerapp -g MyResourceGroup --cpu 0.75 --memory 1.5Gi
264264
"""
265265

266+
helps['containerapp revision label'] = """
267+
type: group
268+
short-summary: Manage revision labels assigned to traffic weights.
269+
"""
270+
271+
helps['containerapp revision label add'] = """
272+
type: command
273+
short-summary: Set a revision label to a revision with an associated traffic weight.
274+
examples:
275+
- name: Add a label to the latest revision.
276+
text: |
277+
az containerapp revision label add -n MyContainerapp -g MyResourceGroup --label myLabel --revision latest
278+
- name: Add a label to a previous revision.
279+
text: |
280+
az containerapp revision label add -g MyResourceGroup --label myLabel --revision revisionName
281+
"""
282+
283+
helps['containerapp revision label remove'] = """
284+
type: command
285+
short-summary: Remove a revision label from a revision with an associated traffic weight.
286+
examples:
287+
- name: Remove a label.
288+
text: |
289+
az containerapp revision label remove -n MyContainerapp -g MyResourceGroup --label myLabel
290+
"""
291+
266292
# Environment Commands
267293
helps['containerapp env'] = """
268294
type: group
@@ -356,6 +382,48 @@
356382
az containerapp env dapr-component remove -g MyResourceGroup --dapr-component-name MyDaprComponentName --name MyEnvironment
357383
"""
358384

385+
helps['containerapp env storage'] = """
386+
type: group
387+
short-summary: Commands to manage storage for the Container Apps environment.
388+
"""
389+
390+
helps['containerapp env storage list'] = """
391+
type: command
392+
short-summary: List the storages for an environment.
393+
examples:
394+
- name: List the storages for an environment.
395+
text: |
396+
az containerapp env storage list -g MyResourceGroup -n MyEnvironment
397+
"""
398+
399+
helps['containerapp env storage show'] = """
400+
type: command
401+
short-summary: Show the details of a storage.
402+
examples:
403+
- name: Show the details of a storage.
404+
text: |
405+
az containerapp env storage show -g MyResourceGroup --storage-name MyStorageName -n MyEnvironment
406+
"""
407+
408+
helps['containerapp env storage set'] = """
409+
type: command
410+
short-summary: Create or update a storage.
411+
examples:
412+
- name: Create a storage.
413+
text: |
414+
az containerapp env storage set -g MyResourceGroup -n MyEnv --storage-name MyStorageName --access-mode ReadOnly --azure-file-account-key MyAccountKey --azure-file-account-name MyAccountName --azure-file-share-name MyShareName
415+
"""
416+
417+
helps['containerapp env storage remove'] = """
418+
type: command
419+
short-summary: Remove a storage from an environment.
420+
examples:
421+
- name: Remove a storage from a Container Apps environment.
422+
text: |
423+
az containerapp env storage remove -g MyResourceGroup --storage-name MyStorageName -n MyEnvironment
424+
"""
425+
426+
359427
# Identity Commands
360428
helps['containerapp identity'] = """
361429
type: group
@@ -459,12 +527,18 @@
459527
type: command
460528
short-summary: Configure traffic-splitting for a container app.
461529
examples:
462-
- name: Route 100%% of a container app's traffic to its latest revision.
530+
- name: Route 100% of a container app's traffic to its latest revision.
463531
text: |
464-
az containerapp ingress traffic set -n MyContainerapp -g MyResourceGroup --traffic-weight latest=100
532+
az containerapp ingress traffic set -n MyContainerapp -g MyResourceGroup --revision-weight latest=100
465533
- name: Split a container app's traffic between two revisions.
466534
text: |
467-
az containerapp ingress traffic set -n MyContainerapp -g MyResourceGroup --traffic-weight latest=80 MyRevisionName=20
535+
az containerapp ingress traffic set -n MyContainerapp -g MyResourceGroup --revision-weight latest=80 MyRevisionName=20
536+
- name: Split a container app's traffic between two labels.
537+
text: |
538+
az containerapp ingress traffic set -n MyContainerapp -g MyResourceGroup --label-weight myLabel=80 myLabel2=20
539+
- name: Split a container app's traffic between a label and a revision.
540+
text: |
541+
az containerapp ingress traffic set -n MyContainerapp -g MyResourceGroup --revision-weight latest=80 --label-weight myLabel=20
468542
"""
469543

470544
helps['containerapp ingress traffic show'] = """

src/containerapp/azext_containerapp/_models.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,3 +231,10 @@
231231
"tenantId": None, # str
232232
"subscriptionId": None # str
233233
}
234+
235+
AzureFileProperties = {
236+
"accountName": None,
237+
"accountKey": None,
238+
"accessMode": None,
239+
"shareName": None
240+
}

src/containerapp/azext_containerapp/_params.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ def load_arguments(self, _):
135135
c.argument('docker_bridge_cidr', options_list=['--docker-bridge-cidr'], help='CIDR notation IP range assigned to the Docker bridge. It must not overlap with any Subnet IP ranges or the IP range defined in Platform Reserved CIDR, if defined')
136136
c.argument('platform_reserved_cidr', options_list=['--platform-reserved-cidr'], help='IP range in CIDR notation that can be reserved for environment infrastructure IP addresses. It must not overlap with any other Subnet IP ranges')
137137
c.argument('platform_reserved_dns_ip', options_list=['--platform-reserved-dns-ip'], help='An IP address from the IP range defined by Platform Reserved CIDR that will be reserved for the internal DNS server.')
138-
c.argument('internal_only', arg_type=get_three_state_flag(), options_list=['--internal-only'], help='Boolean indicating the environment only has an internal load balancer. These environments do not have a public static IP resource, therefore must provide infrastructureSubnetResourceId and appSubnetResourceId if enabling this property')
138+
c.argument('internal_only', arg_type=get_three_state_flag(), options_list=['--internal-only'], help='Boolean indicating the environment only has an internal load balancer. These environments do not have a public static IP resource, therefore must provide infrastructureSubnetResourceId if enabling this property')
139139

140140
with self.argument_context('containerapp env update') as c:
141141
c.argument('name', name_type, help='Name of the Container Apps environment.')
@@ -147,6 +147,14 @@ def load_arguments(self, _):
147147
with self.argument_context('containerapp env show') as c:
148148
c.argument('name', name_type, help='Name of the Container Apps Environment.')
149149

150+
with self.argument_context('containerapp env storage') as c:
151+
c.argument('name', id_part=None)
152+
c.argument('storage_name', help="Name of the storage.")
153+
c.argument('access_mode', id_part=None, arg_type=get_enum_type(["ReadWrite", "ReadOnly"]), help="Access mode for the AzureFile storage.")
154+
c.argument('azure_file_account_key', options_list=["--azure-file-account-key", "--storage-account-key", "-k"], help="Key of the AzureFile storage account.")
155+
c.argument('azure_file_share_name', options_list=["--azure-file-share-name", "--file-share", "-f"], help="Name of the share on the AzureFile storage.")
156+
c.argument('azure_file_account_name', options_list=["--azure-file-account-name", "--account-name", "-a"], help="Name of the AzureFile storage account.")
157+
150158
with self.argument_context('containerapp identity') as c:
151159
c.argument('user_assigned', nargs='+', help="Space-separated user identities.")
152160
c.argument('system_assigned', help="Boolean indicating whether to assign system-assigned identity.")
@@ -157,7 +165,7 @@ def load_arguments(self, _):
157165
with self.argument_context('containerapp github-action add') as c:
158166
c.argument('repo_url', help='The GitHub repository to which the workflow file will be added. In the format: https://github.com/<owner>/<repository-name>')
159167
c.argument('token', help='A Personal Access Token with write access to the specified repository. For more information: https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line')
160-
c.argument('branch', options_list=['--branch', '-b'], help='The branch of the GitHub repo. Defaults to "main" if not specified.')
168+
c.argument('branch', options_list=['--branch', '-b'], help='The branch of the Github repo. Assumed to be the Github repo\'s default branch if not specified.')
161169
c.argument('login_with_github', help='Interactively log in with Github to retrieve the Personal Access Token')
162170
c.argument('registry_url', help='The container registry server, e.g. myregistry.azurecr.io')
163171
c.argument('registry_username', help='The username of the registry. If using Azure Container Registry, we will try to infer the credentials if not supplied')
@@ -179,14 +187,20 @@ def load_arguments(self, _):
179187
c.argument('from_revision', help='Revision to copy from. Default: latest revision.')
180188
c.argument('image', options_list=['--image', '-i'], help="Container image, e.g. publisher/image-name:tag.")
181189

190+
with self.argument_context('containerapp revision label') as c:
191+
c.argument('name', id_part=None)
192+
c.argument('revision', help='Name of the revision.')
193+
c.argument('label', help='Name of the label.')
194+
182195
with self.argument_context('containerapp ingress') as c:
183196
c.argument('allow_insecure', help='Allow insecure connections for ingress traffic.')
184197
c.argument('type', validator=validate_ingress, arg_type=get_enum_type(['internal', 'external']), help="The ingress type.")
185198
c.argument('transport', arg_type=get_enum_type(['auto', 'http', 'http2']), help="The transport protocol used for ingress traffic.")
186199
c.argument('target_port', type=int, validator=validate_target_port, help="The application port used for ingress traffic.")
187200

188201
with self.argument_context('containerapp ingress traffic') as c:
189-
c.argument('traffic_weights', nargs='*', options_list=['--traffic-weight'], help="A list of revision weight(s) for the container app. Space-separated values in 'revision_name=weight' format. For latest revision, use 'latest=weight'")
202+
c.argument('revision_weights', nargs='+', options_list=['--revision-weight', c.deprecate(target='--traffic-weight', redirect='--revision-weight')], help="A list of revision weight(s) for the container app. Space-separated values in 'revision_name=weight' format. For latest revision, use 'latest=weight'")
203+
c.argument('label_weights', nargs='+', options_list=['--label-weight'], help="A list of label weight(s) for the container app. Space-separated values in 'label_name=weight' format.")
190204

191205
with self.argument_context('containerapp secret') as c:
192206
c.argument('secrets', nargs='+', options_list=['--secrets', '-s'], help="A list of secret(s) for the container app. Space-separated values in 'key=value' format (where 'key' cannot be longer than 20 characters).")
@@ -235,8 +249,8 @@ def load_arguments(self, _):
235249

236250
with self.argument_context('containerapp up', arg_group='Github Repo') as c:
237251
c.argument('repo', help='Create an app via Github Actions. In the format: https://github.com/<owner>/<repository-name> or <owner>/<repository-name>')
238-
c.argument('token', help='A Personal Access Token with write access to the specified repository. For more information: https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line. If missing (and using --repo), a browser page will be opened to authenticate with Github.')
239-
c.argument('branch', options_list=['--branch', '-b'], help='The branch of the GitHub repo. Defaults to "main"')
252+
c.argument('token', help='A Personal Access Token with write access to the specified repository. For more information: https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line. If not provided or not found in the cache (and using --repo), a browser page will be opened to authenticate with Github.')
253+
c.argument('branch', options_list=['--branch', '-b'], help='The branch of the Github repo. Assumed to be the Github repo\'s default branch if not specified.')
240254
c.argument('context_path', help='Path in the repo from which to run the docker build. Defaults to "./". Dockerfile is assumed to be named "Dockerfile" and in this directory.')
241255
c.argument('service_principal_client_id', help='The service principal client ID. Used by Github Actions to authenticate with Azure.', options_list=["--service-principal-client-id", "--sp-cid"])
242256
c.argument('service_principal_client_secret', help='The service principal client secret. Used by Github Actions to authenticate with Azure.', options_list=["--service-principal-client-secret", "--sp-sec"])

src/containerapp/azext_containerapp/_ssh_utils.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# Copyright (c) Microsoft Corporation. All rights reserved.
33
# Licensed under the MIT License. See License.txt in the project root for license information.
44
# --------------------------------------------------------------------------------------------
5+
# pylint: disable=logging-fstring-interpolation
56

67
import os
78
import sys
@@ -50,8 +51,6 @@
5051

5152
class WebSocketConnection:
5253
def __init__(self, cmd, resource_group_name, name, revision, replica, container, startup_command):
53-
from websocket._exceptions import WebSocketBadStatusException
54-
5554
token_response = ContainerAppClient.get_auth_token(cmd, resource_group_name, name)
5655
self._token = token_response["properties"]["token"]
5756
self._logstream_endpoint = token_response["properties"]["logStreamEndpoint"]

0 commit comments

Comments
 (0)