Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(kpack): enable build log streaming during app deploy #13

Merged
merged 1 commit into from
Aug 13, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 67 additions & 5 deletions src/spring-cloud/azext_spring_cloud/_enterprise.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
from ._utils import get_azure_files_info, _pack_source_code
from azure.cli.core.util import sdk_no_wait
from .azure_storage_file import FileService
from requests.auth import HTTPBasicAuth
import os
import requests
import re
import sys
import tempfile
import uuid

Expand Down Expand Up @@ -229,27 +233,41 @@ def _build_and_get_result(cmd, client, resource_group, service, name, version, a
logger.warning("[3/{}] Creating or Updating build '{}'.".format(total_steps, name))
build_result_id = _queue_build(client, resource_group, service, name, relative_path, target_module)
logger.warning("[4/{}] Waiting for building docker image to finish. This may take a few minutes.".format(total_steps))
_wait_build_finished(cmd, client, build_result_id)
_wait_build_finished(cmd, client, service, build_result_id)
return models.BuildResultUserSourceInfo(version=version, build_result_id=build_result_id)


def _wait_build_finished(cmd, client, build_result_id):
def _wait_build_finished(cmd, client, service, build_result_id):
resource_id = parse_resource_id(build_result_id)
resource_group = resource_id['resource_group']
service = resource_id['name']
build_service = resource_id['child_name_1']
build = resource_id['child_name_2']
build_result_name = resource_id['resource_name']

# Wait until build pod started
progress_bar = cmd.cli_ctx.get_progress_controller()
result = client.build_service.get_build_result(resource_group, service, build_service, build, build_result_name)
progress_bar.add(message=result.properties.status)
progress_bar.begin()
while result.properties.status == "Building" or result.properties.status == "Queuing":
result = client.build_service.get_build_result(resource_group, service, build_service, build, build_result_name)
while (not result.properties.build_pod_name or not result.properties.build_stages) and (result.properties.status == "Building" or result.properties.status == "Queuing"):
yuwzho marked this conversation as resolved.
Show resolved Hide resolved
progress_bar.add(message=result.properties.status)
sleep(5)
result = client.build_service.get_build_result(resource_group, service, build_service, build, build_result_name)
progress_bar.stop()

# Try to get build logs (failures are not fatal)
if result.properties.build_pod_name and result.properties.build_stages:
for stage in result.properties.build_stages:
_start_build_log_streaming(client, resource_group, service, result.properties.build_pod_name, stage.name)
else:
logger.warning("Cannot show build logs, but will still wait for the build job.")

# Wait until build finished
result = client.build_service.get_build_result(resource_group, service, build_service, build, build_result_name)
while result.properties.status == "Building" or result.properties.status == "Queuing":
sleep(5)
yuwzho marked this conversation as resolved.
Show resolved Hide resolved
result = client.build_service.get_build_result(resource_group, service, build_service, build, build_result_name)

if result.properties.status != "Succeeded":
raise CLIError("Failed to build docker image, please check the build logs and retry.")

Expand Down Expand Up @@ -449,3 +467,47 @@ def _wait_till_end(cmd, *pollers):
while any(x and not x.done() for x in pollers):
progress_bar.add(message='Running')
sleep(5)

def _start_build_log_streaming(client, resource_group, service, pod_name, stage_name):
if not pod_name or not stage_name:
return

logger.info("------------------------- %s -------------------------", stage_name)

# TODO: try to merge shared log streaming logic with `app_tail_log` function
test_keys = client.services.list_test_keys(resource_group, service)
primary_key = test_keys.primary_key
if not primary_key:
logger.warning("To use the log streaming feature, please enable the test endpoint by running 'az spring-cloud test-endpoint enable -n {0} -g {1}'".format(service, resource_group))
return

test_url = test_keys.primary_test_endpoint
base_url = test_url.replace('.test.', '.')
base_url = re.sub('https://.+?\@', '', base_url)
streaming_url = "https://{}/api/logstream/buildpods/{}/stages/{}?follow=true".format(base_url, pod_name, stage_name)

need_retry = True
max_retry_count = 15
retry_count = 0
while need_retry:
retry_count = retry_count + 1
if retry_count >= max_retry_count:
logger.warning("Failed to get build logs due to time-out")
return
with requests.get(streaming_url, stream=True, auth=HTTPBasicAuth("primary", primary_key)) as response:
if response.status_code == 200:
need_retry = False
std_encoding = sys.stdout.encoding
for content in response.iter_content():
if content:
sys.stdout.write(content.decode(encoding='utf-8', errors='replace')
.encode(std_encoding, errors='replace')
.decode(std_encoding, errors='replace'))
elif response.status_code == 400:
# Container not started yet
sleep(2)
# TODO: Fail fast if build result already failed
else:
logger.warning("Failed to get build logs with status code '{}' and reason '{}'".format(
response.status_code, response.content))
return