Skip to content

Commit

Permalink
Merge pull request Azure#7 from StrawnSC/eventstream
Browse files Browse the repository at this point in the history
Eventstream
  • Loading branch information
StrawnSC authored Oct 4, 2022
2 parents 6f5642f + ff63c2c commit 66f0ddb
Show file tree
Hide file tree
Showing 11 changed files with 2,792 additions and 786 deletions.
2 changes: 2 additions & 0 deletions src/containerapp/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ Release History
++++++
* Add 'az containerapp env update' to update managed environment properties
* Add custom domains support to 'az containerapp env create' and 'az containerapp env update'
* 'az containerapp logs show': add new parameter "--kind" to allow showing system logs
* Show system environment logs with new command 'az containerapp env logs show'
* Add tcp support for ingress transport and scale rules
* `az containerapp up/github-action add`: Retrieve workflow file name from github actions API
* 'az containerapp create/update': validate revision suffixes
Expand Down
18 changes: 17 additions & 1 deletion src/containerapp/azext_containerapp/_clients.py
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,7 @@ def get_replica(cls, cmd, resource_group_name, container_app_name, revision_name
def get_auth_token(cls, cmd, resource_group_name, name):
management_hostname = cmd.cli_ctx.cloud.endpoints.resource_manager
sub_id = get_subscription_id(cmd.cli_ctx)
url_fmt = "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.App/containerApps/{}/authtoken?api-version={}"
url_fmt = "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.App/containerApps/{}/getAuthToken?api-version={}"
request_url = url_fmt.format(
management_hostname.strip('/'),
sub_id,
Expand Down Expand Up @@ -690,6 +690,22 @@ def check_name_availability(cls, cmd, resource_group_name, name, name_availabili
return r.json()


@classmethod
def get_auth_token(cls, cmd, resource_group_name, name):
management_hostname = cmd.cli_ctx.cloud.endpoints.resource_manager
sub_id = get_subscription_id(cmd.cli_ctx)
url_fmt = "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.App/managedEnvironments/{}/getAuthToken?api-version={}"
request_url = url_fmt.format(
management_hostname.strip('/'),
sub_id,
resource_group_name,
name,
CURRENT_API_VERSION)

r = send_raw_request(cmd.cli_ctx, "POST", request_url)
return r.json()


class GitHubActionClient():
@classmethod
def create_or_update(cls, cmd, resource_group_name, name, github_action_envelope, headers, no_wait=False):
Expand Down
3 changes: 3 additions & 0 deletions src/containerapp/azext_containerapp/_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
NAME_INVALID = "Invalid"
NAME_ALREADY_EXISTS = "AlreadyExists"

LOG_TYPE_CONSOLE = "console"
LOG_TYPE_SYSTEM = "system"

ACR_TASK_TEMPLATE = """version: v1.1.0
steps:
- cmd: mcr.microsoft.com/oryx/cli:20220811.1 oryx dockerfile --bind-port {{target_port}} --output ./Dockerfile .
Expand Down
22 changes: 21 additions & 1 deletion src/containerapp/azext_containerapp/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,18 +155,38 @@
az containerapp up -n MyContainerapp --image myregistry.azurecr.io/myImage:myTag --ingress external --target-port 80 --environment MyEnv
"""

helps['containerapp env logs'] = """
type: group
short-summary: Show container app environment logs
"""

helps['containerapp env logs show'] = """
type: command
short-summary: Show past environment logs and/or print logs in real time (with the --follow parameter)
examples:
- name: Fetch the past 20 lines of logs from an app and return
text: |
az containerapp env logs show -n MyEnvironment -g MyResourceGroup
- name: Fetch 30 lines of past logs logs from an environment and print logs as they come in
text: |
az containerapp env logs show -n MyEnvironment -g MyResourceGroup --follow --tail 30
"""

helps['containerapp logs'] = """
type: group
short-summary: Show container app logs
"""

helps['containerapp logs show'] = """
type: command
short-summary: Show past logs and/or print logs in real time (with the --follow parameter). Note that the logs are only taken from one revision, replica, and container.
short-summary: Show past logs and/or print logs in real time (with the --follow parameter). Note that the logs are only taken from one revision, replica, and container (for non-system logs).
examples:
- name: Fetch the past 20 lines of logs from an app and return
text: |
az containerapp logs show -n MyContainerapp -g MyResourceGroup
- name: Fetch the past 20 lines of system logs from an app and return
text: |
az containerapp logs show -n MyContainerapp -g MyResourceGroup --kind system
- name: Fetch 30 lines of past logs logs from an app and print logs as they come in
text: |
az containerapp logs show -n MyContainerapp -g MyResourceGroup --follow --tail 30
Expand Down
7 changes: 6 additions & 1 deletion src/containerapp/azext_containerapp/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

from ._validators import (validate_memory, validate_cpu, validate_managed_env_name_or_id, validate_registry_server,
validate_registry_user, validate_registry_pass, validate_target_port, validate_ingress)
from ._constants import UNAUTHENTICATED_CLIENT_ACTION, FORWARD_PROXY_CONVENTION, MAXIMUM_CONTAINER_APP_NAME_LENGTH
from ._constants import UNAUTHENTICATED_CLIENT_ACTION, FORWARD_PROXY_CONVENTION, MAXIMUM_CONTAINER_APP_NAME_LENGTH, LOG_TYPE_CONSOLE, LOG_TYPE_SYSTEM


def load_arguments(self, _):
Expand Down Expand Up @@ -48,6 +48,11 @@ def load_arguments(self, _):
c.argument('revision', help="The name of the container app revision. Defaults to the latest revision.")
c.argument('name', name_type, id_part=None, help="The name of the Containerapp.")
c.argument('resource_group_name', arg_type=resource_group_name_type, id_part=None)
c.argument('kind', options_list=["--kind", "-k"], help="Type of logs to stream", arg_type=get_enum_type([LOG_TYPE_CONSOLE, LOG_TYPE_SYSTEM]), default=LOG_TYPE_CONSOLE)

with self.argument_context('containerapp env logs show') as c:
c.argument('follow', help="Print logs in real time if present.", arg_type=get_three_state_flag())
c.argument('tail', help="The number of past logs to print (0-300)", type=int, default=20)

# Replica
with self.argument_context('containerapp replica') as c:
Expand Down
13 changes: 7 additions & 6 deletions src/containerapp/azext_containerapp/_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@
MutuallyExclusiveArgumentError)
from msrestazure.tools import is_valid_resource_id
from knack.log import get_logger
import re

from ._clients import ContainerAppClient
from ._ssh_utils import ping_container_app
from ._utils import safe_get, is_registry_msi_system
from ._constants import ACR_IMAGE_SUFFIX
import re
from ._constants import ACR_IMAGE_SUFFIX, LOG_TYPE_SYSTEM


logger = get_logger(__name__)
Expand Down Expand Up @@ -182,7 +182,8 @@ def _validate_container_exists(cmd, namespace):

# also used to validate logstream
def validate_ssh(cmd, namespace):
_set_ssh_defaults(cmd, namespace)
_validate_revision_exists(cmd, namespace)
_validate_replica_exists(cmd, namespace)
_validate_container_exists(cmd, namespace)
if not hasattr(namespace, "kind") or (namespace.kind and namespace.kind.lower() != LOG_TYPE_SYSTEM):
_set_ssh_defaults(cmd, namespace)
_validate_revision_exists(cmd, namespace)
_validate_replica_exists(cmd, namespace)
_validate_container_exists(cmd, namespace)
2 changes: 2 additions & 0 deletions src/containerapp/azext_containerapp/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ def load_command_table(self, _):

with self.command_group('containerapp logs') as g:
g.custom_show_command('show', 'stream_containerapp_logs', validator=validate_ssh)
with self.command_group('containerapp env logs') as g:
g.custom_show_command('show', 'stream_environment_logs')

with self.command_group('containerapp env') as g:
g.custom_show_command('show', 'show_managed_environment')
Expand Down
63 changes: 57 additions & 6 deletions src/containerapp/azext_containerapp/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
SSH_BACKUP_ENCODING)
from ._constants import (MAXIMUM_SECRET_LENGTH, MICROSOFT_SECRET_SETTING_NAME, FACEBOOK_SECRET_SETTING_NAME, GITHUB_SECRET_SETTING_NAME,
GOOGLE_SECRET_SETTING_NAME, TWITTER_SECRET_SETTING_NAME, APPLE_SECRET_SETTING_NAME, CONTAINER_APPS_RP,
NAME_INVALID, NAME_ALREADY_EXISTS, ACR_IMAGE_SUFFIX, HELLO_WORLD_IMAGE)
NAME_INVALID, NAME_ALREADY_EXISTS, ACR_IMAGE_SUFFIX, HELLO_WORLD_IMAGE, LOG_TYPE_SYSTEM, LOG_TYPE_CONSOLE)

logger = get_logger(__name__)

Expand Down Expand Up @@ -2434,24 +2434,75 @@ def containerapp_ssh(cmd, resource_group_name, name, container=None, revision=No


def stream_containerapp_logs(cmd, resource_group_name, name, container=None, revision=None, replica=None, follow=False,
tail=None, output_format=None):
tail=None, output_format=None, kind=None):
if tail:
if tail < 0 or tail > 300:
raise ValidationError("--tail must be between 0 and 300.")
if kind == LOG_TYPE_SYSTEM:
if container or replica or revision:
raise MutuallyExclusiveArgumentError("--kind: --container, --replica, and --revision not supported for system logs")
if output_format != "json":
raise MutuallyExclusiveArgumentError("--kind: only json logs supported for system logs")


sub = get_subscription_id(cmd.cli_ctx)
token_response = ContainerAppClient.get_auth_token(cmd, resource_group_name, name)
token = token_response["properties"]["token"]
logstream_endpoint = token_response["properties"]["logStreamEndpoint"]
base_url = logstream_endpoint[:logstream_endpoint.index("/subscriptions/")]

url = (f"{base_url}/subscriptions/{sub}/resourceGroups/{resource_group_name}/containerApps/{name}"
f"/revisions/{revision}/replicas/{replica}/containers/{container}/logstream")
if kind == LOG_TYPE_CONSOLE:
url = (f"{base_url}/subscriptions/{sub}/resourceGroups/{resource_group_name}/containerApps/{name}"
f"/revisions/{revision}/replicas/{replica}/containers/{container}/logstream")
else:
url = (f"{base_url}/subscriptions/{sub}/resourceGroups/{resource_group_name}/containerApps/{name}"
f"/eventstream")

logger.info("connecting to : %s", url)
request_params = {"follow": str(follow).lower(),
"output": output_format,
"tailLines": tail}
headers = {"Authorization": f"Bearer {token}"}
resp = requests.get(url,
timeout=None,
stream=True,
params=request_params,
headers=headers)

if not resp.ok:
ValidationError(f"Got bad status from the logstream API: {resp.status_code}")

for line in resp.iter_lines():
if line:
logger.info("received raw log line: %s", line)
# these .replaces are needed to display color/quotations properly
# for some reason the API returns garbled unicode special characters (may need to add more in the future)
print(line.decode("utf-8").replace("\\u0022", "\u0022").replace("\\u001B", "\u001B").replace("\\u002B", "\u002B").replace("\\u0027", "\u0027"))


def stream_environment_logs(cmd, resource_group_name, name, follow=False, tail=None):
if tail:
if tail < 0 or tail > 300:
raise ValidationError("--tail must be between 0 and 300.")

env = show_managed_environment(cmd, name, resource_group_name)
sub = get_subscription_id(cmd.cli_ctx)
token_response = ManagedEnvironmentClient.get_auth_token(cmd, resource_group_name, name)
token = token_response["properties"]["token"]
base_url = f"https://{env['location']}.azurecontainerapps.dev"

url = (f"{base_url}/subscriptions/{sub}/resourceGroups/{resource_group_name}/managedEnvironments/{name}"
f"/eventstream")

logger.info("connecting to : %s", url)
request_params = {"follow": str(follow).lower(), "output": output_format, "tailLines": tail}
request_params = {"follow": str(follow).lower(),
"tailLines": tail}
headers = {"Authorization": f"Bearer {token}"}
resp = requests.get(url, timeout=None, stream=True, params=request_params, headers=headers)
resp = requests.get(url,
timeout=None,
stream=True,
params=request_params,
headers=headers)

if not resp.ok:
ValidationError(f"Got bad status from the logstream API: {resp.status_code}")
Expand Down
Loading

0 comments on commit 66f0ddb

Please sign in to comment.