pybuilder-docker-too 47__tar.gz → 48__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pybuilder-docker-too
3
- Version: 47
3
+ Version: 48
4
4
  Summary: A pybuilder plugin that stages a python package into a docker container and optionally publishes it to a registry.
5
5
  Home-page: https://github.com/rspitler/pybuilder-docker
6
6
  Author:
@@ -41,10 +41,15 @@ RUN ${package_cmd}
41
41
  """)
42
42
 
43
43
  DEFAULT_DOCKER_COMPOSE_READY_TIMEOUT_SECONDS = 180
44
- DEFAULT_DOCKER_COMPOSE_READY_POLL_INTERVAL_SECONDS = 2
44
+ DEFAULT_DOCKER_COMPOSE_READY_POLL_INTERVAL_SECONDS = 1
45
+ DEFAULT_DOCKER_COMPOSE_DOWN_TIMEOUT_SECONDS = 1
45
46
  COMPOSE_DIAGNOSTIC_LOG_TAIL = 200
46
47
  COMPOSE_DIAGNOSTIC_MAX_BYTES = 1024 * 1024
47
48
  _DEFAULT_SERVICES = object()
49
+ _PRESTARTED_COMPOSE_SERVICES_PROPERTY = (
50
+ "_pybuilder_docker_prestarted_compose_services")
51
+ _INFERRED_PRETEST_SERVICES_PROPERTY = (
52
+ "_pybuilder_docker_inferred_pretest_services")
48
53
 
49
54
 
50
55
  @init
@@ -56,6 +61,12 @@ def init_plugin(project: Project, logger: Logger, reactor: Reactor):
56
61
  project.set_property_if_unset(
57
62
  'docker_compose_ready_poll_interval',
58
63
  DEFAULT_DOCKER_COMPOSE_READY_POLL_INTERVAL_SECONDS)
64
+ project.set_property_if_unset(
65
+ 'docker_compose_down_timeout',
66
+ DEFAULT_DOCKER_COMPOSE_DOWN_TIMEOUT_SECONDS)
67
+ project.set_property_if_unset(
68
+ 'docker_compose_prestart_during_package',
69
+ False)
59
70
 
60
71
 
61
72
  @task(description="Package artifact into a docker container with the tag "
@@ -109,6 +120,7 @@ def do_docker_package(project, logger, reactor):
109
120
  if ecr_login_before_build:
110
121
  registry = project.get_mandatory_property("docker_push_registry")
111
122
  _ecr_login(project, registry, logger, reactor)
123
+ _prestart_compose_services_during_package(project, logger)
112
124
  dist_dir = prepare_dist_directory(project)
113
125
  reactor.pybuilder_venv.verify_can_execute(["docker", "--version"], prerequisite="docker", caller="docker_package")
114
126
  # is true if user set verbose in build.py or from command line
@@ -661,6 +673,62 @@ def _run_compose_command(
661
673
  return output
662
674
 
663
675
 
676
+ def _prestart_compose_services_during_package(project, logger):
677
+ if not project.get_property(
678
+ "docker_compose_prestart_during_package", False):
679
+ return []
680
+ if not project.get_property("run_docker_compose_on_verify", False):
681
+ return []
682
+ if not (
683
+ project.get_property("run_pretest_executable", False)
684
+ and project.get_property("pretest_executable", None)):
685
+ return []
686
+ if _normalize_required_services(
687
+ project.get_property("docker_compose_pretest_services", None)):
688
+ logger.debug(
689
+ "Skipping early Compose prestart because pretest services "
690
+ "are explicitly configured")
691
+ return []
692
+
693
+ compose_file = _get_docker_compose_file(project)
694
+ if not compose_file:
695
+ logger.debug(
696
+ "Skipping early Compose prestart because the Compose file "
697
+ "is not prepared yet")
698
+ return []
699
+
700
+ timeout_seconds = _to_number(
701
+ project.get_property(
702
+ "docker_compose_ready_timeout",
703
+ DEFAULT_DOCKER_COMPOSE_READY_TIMEOUT_SECONDS),
704
+ DEFAULT_DOCKER_COMPOSE_READY_TIMEOUT_SECONDS)
705
+ if not math.isfinite(timeout_seconds) or timeout_seconds < 0:
706
+ raise ValueError(
707
+ "docker_compose_ready_timeout must be finite and >= 0")
708
+ deadline = time.monotonic() + timeout_seconds
709
+ compose_env = _compose_environment(project)
710
+ inferred_services = _list_compose_healthy_dependencies(
711
+ compose_file, compose_env, deadline)
712
+ if not inferred_services:
713
+ return []
714
+
715
+ logger.info(
716
+ "Starting Compose infrastructure during image packaging ({})".format(
717
+ ", ".join(inferred_services)))
718
+ _run_compose_command(
719
+ compose_file,
720
+ ["up", "--detach", "--no-deps"] + inferred_services,
721
+ compose_env,
722
+ _remaining_timeout(deadline, time.monotonic))
723
+ project.set_property(
724
+ _PRESTARTED_COMPOSE_SERVICES_PROPERTY,
725
+ list(inferred_services))
726
+ project.set_property(
727
+ _INFERRED_PRETEST_SERVICES_PROPERTY,
728
+ list(inferred_services))
729
+ return inferred_services
730
+
731
+
664
732
  def _capture_compose_service_logs(
665
733
  compose_file,
666
734
  pending,
@@ -830,6 +898,8 @@ def _wait_for_compose_services(
830
898
  last_services = []
831
899
  last_inspect_error = None
832
900
  last_service_logs = ""
901
+ logs_captured = False
902
+ last_log_capture_pending = None
833
903
  while True:
834
904
  if compose_process is not None:
835
905
  exit_code = compose_process.poll()
@@ -860,24 +930,33 @@ def _wait_for_compose_services(
860
930
  logger.info("Required compose services are ready")
861
931
  return last_services
862
932
 
863
- try:
864
- captured_service_logs = logs_fn(
865
- compose_file,
866
- last_pending,
867
- compose_env,
868
- deadline,
869
- time_fn)
870
- if (
871
- captured_service_logs
872
- and not captured_service_logs.startswith(
873
- "<unable to collect service logs:")):
874
- last_service_logs = captured_service_logs
875
- elif not last_service_logs:
876
- last_service_logs = captured_service_logs
877
- except Exception as exc:
878
- if not last_service_logs:
879
- last_service_logs = (
880
- "<unable to collect service logs: {}>".format(exc))
933
+ remaining_before_logs = deadline - time_fn()
934
+ pending_key = tuple(sorted(last_pending))
935
+ should_capture_logs = (
936
+ not logs_captured
937
+ or pending_key != last_log_capture_pending
938
+ or 0 < remaining_before_logs <= poll_interval)
939
+ if should_capture_logs:
940
+ try:
941
+ captured_service_logs = logs_fn(
942
+ compose_file,
943
+ last_pending,
944
+ compose_env,
945
+ deadline,
946
+ time_fn)
947
+ if (
948
+ captured_service_logs
949
+ and not captured_service_logs.startswith(
950
+ "<unable to collect service logs:")):
951
+ last_service_logs = captured_service_logs
952
+ elif not last_service_logs:
953
+ last_service_logs = captured_service_logs
954
+ except Exception as exc:
955
+ if not last_service_logs:
956
+ last_service_logs = (
957
+ "<unable to collect service logs: {}>".format(exc))
958
+ logs_captured = True
959
+ last_log_capture_pending = pending_key
881
960
 
882
961
  remaining = deadline - time_fn()
883
962
  if remaining <= 0:
@@ -951,10 +1030,12 @@ def _run_docker_compose(project, logger, reactor: Reactor):
951
1030
  "up"]
952
1031
 
953
1032
  expected_services = None
1033
+ application_services = None
954
1034
  pretest_enabled = (
955
1035
  project.get_property("run_pretest_executable", False)
956
1036
  and project.get_property("pretest_executable", None))
957
1037
  if pretest_enabled:
1038
+ pretest_services_started_at = time.monotonic()
958
1039
  timeout_seconds = _to_number(
959
1040
  project.get_property(
960
1041
  "docker_compose_ready_timeout",
@@ -966,10 +1047,15 @@ def _run_docker_compose(project, logger, reactor: Reactor):
966
1047
  setup_deadline = time.monotonic() + timeout_seconds
967
1048
  expected_services = _list_compose_services(
968
1049
  compose_file, compose_env, setup_deadline)
969
- inferred_pretest_services = None
970
- if not _normalize_required_services(
971
- project.get_property(
972
- "docker_compose_pretest_services", None)):
1050
+ inferred_pretest_services = _normalize_required_services(
1051
+ project.get_property(
1052
+ _INFERRED_PRETEST_SERVICES_PROPERTY, None))
1053
+ configured_pretest_services = _normalize_required_services(
1054
+ project.get_property(
1055
+ "docker_compose_pretest_services", None))
1056
+ if (
1057
+ not configured_pretest_services
1058
+ and not inferred_pretest_services):
973
1059
  inferred_pretest_services = (
974
1060
  _list_compose_healthy_dependencies(
975
1061
  compose_file, compose_env, setup_deadline))
@@ -987,14 +1073,27 @@ def _run_docker_compose(project, logger, reactor: Reactor):
987
1073
  project,
988
1074
  created_services,
989
1075
  inferred_services=inferred_pretest_services)
990
- start_output = _run_compose_command(
991
- compose_file,
992
- ["up", "--detach", "--no-deps"] + pretest_services,
993
- compose_env,
994
- _remaining_timeout(setup_deadline, time.monotonic))
995
- if start_output:
996
- fp.write(start_output)
997
- fp.flush()
1076
+ pretest_service_names = set(pretest_services)
1077
+ application_services = [
1078
+ service for service in expected_services
1079
+ if service not in pretest_service_names]
1080
+ prestarted_services = set(
1081
+ _normalize_required_services(
1082
+ project.get_property(
1083
+ _PRESTARTED_COMPOSE_SERVICES_PROPERTY, None))
1084
+ or [])
1085
+ services_to_start = [
1086
+ service for service in pretest_services
1087
+ if service not in prestarted_services]
1088
+ if services_to_start:
1089
+ start_output = _run_compose_command(
1090
+ compose_file,
1091
+ ["up", "--detach", "--no-deps"] + services_to_start,
1092
+ compose_env,
1093
+ _remaining_timeout(setup_deadline, time.monotonic))
1094
+ if start_output:
1095
+ fp.write(start_output)
1096
+ fp.flush()
998
1097
  _wait_for_compose_services(
999
1098
  project,
1000
1099
  logger,
@@ -1002,8 +1101,19 @@ def _run_docker_compose(project, logger, reactor: Reactor):
1002
1101
  compose_env=compose_env,
1003
1102
  required_services_override=pretest_services,
1004
1103
  deadline_override=setup_deadline)
1104
+ logger.info(
1105
+ "Pretest compose services ready after {:.1f}s".format(
1106
+ time.monotonic() - pretest_services_started_at))
1107
+ pretest_started_at = time.monotonic()
1005
1108
  _run_pretest_executable(project, logger, reactor)
1006
-
1109
+ logger.info(
1110
+ "Pretest executable completed after {:.1f}s".format(
1111
+ time.monotonic() - pretest_started_at))
1112
+
1113
+ if application_services:
1114
+ args += (
1115
+ ["--no-deps", "--no-recreate"]
1116
+ + application_services)
1007
1117
  logger.debug(f"Running docker compose with {args}")
1008
1118
  application_timeout = _to_number(
1009
1119
  project.get_property(
@@ -1014,6 +1124,7 @@ def _run_docker_compose(project, logger, reactor: Reactor):
1014
1124
  raise ValueError(
1015
1125
  "docker_compose_ready_timeout must be finite and >= 0")
1016
1126
  application_deadline = time.monotonic() + application_timeout
1127
+ application_started_at = time.monotonic()
1017
1128
  compose_process = subprocess.Popen(
1018
1129
  args, stderr=fp_err, stdout=fp, env=compose_env)
1019
1130
  running_services = _wait_for_compose_services(
@@ -1024,10 +1135,17 @@ def _run_docker_compose(project, logger, reactor: Reactor):
1024
1135
  compose_env=compose_env,
1025
1136
  expected_services_override=expected_services,
1026
1137
  deadline_override=application_deadline)
1138
+ logger.info(
1139
+ "Compose application services ready after {:.1f}s".format(
1140
+ time.monotonic() - application_started_at))
1141
+ target_started_at = time.monotonic()
1027
1142
  _wait_for_target_url(
1028
1143
  project,
1029
1144
  logger,
1030
1145
  compose_services=running_services)
1146
+ logger.info(
1147
+ "Integration target ready after {:.1f}s".format(
1148
+ time.monotonic() - target_started_at))
1031
1149
  if not pretest_enabled:
1032
1150
  _run_pretest_executable(project, logger, reactor)
1033
1151
 
@@ -1063,12 +1181,22 @@ def _docker_compose_down(project, logger, reactor: Reactor):
1063
1181
  compose_file = _get_docker_compose_file(project)
1064
1182
  if compose_file:
1065
1183
  logger.info(f"Docker Compose Down: {compose_file}")
1184
+ timeout_seconds = _to_number(
1185
+ project.get_property(
1186
+ "docker_compose_down_timeout",
1187
+ DEFAULT_DOCKER_COMPOSE_DOWN_TIMEOUT_SECONDS),
1188
+ DEFAULT_DOCKER_COMPOSE_DOWN_TIMEOUT_SECONDS)
1189
+ if not math.isfinite(timeout_seconds) or timeout_seconds < 0:
1190
+ raise ValueError(
1191
+ "docker_compose_down_timeout must be finite and >= 0")
1066
1192
  # clean up our test run
1067
1193
  exec_command("docker", [
1068
1194
  "compose",
1069
1195
  "-f",
1070
1196
  compose_file,
1071
- "down"
1197
+ "down",
1198
+ "--timeout",
1199
+ str(int(math.ceil(timeout_seconds)))
1072
1200
  ], output_file_name="docker_compose_run", project=project, logger=logger,
1073
1201
  reactor=reactor, env=_compose_environment(project))
1074
1202
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pybuilder-docker-too
3
- Version: 47
3
+ Version: 48
4
4
  Summary: A pybuilder plugin that stages a python package into a docker container and optionally publishes it to a registry.
5
5
  Home-page: https://github.com/rspitler/pybuilder-docker
6
6
  Author:
@@ -21,7 +21,7 @@ class install(_install):
21
21
  if __name__ == '__main__':
22
22
  setup(
23
23
  name = 'pybuilder-docker-too',
24
- version = '47',
24
+ version = '48',
25
25
  description = 'A pybuilder plugin that stages a python package into a docker container and optionally publishes it to a registry.',
26
26
  long_description = 'A pybuilder plugin that stages a python package into a docker container and optionally publishes it to a registry.',
27
27
  long_description_content_type = None,