Skip to content

Commit 6b9d534

Browse files
authored
Set user agent when downloading jars with local_jar (#36342)
* Set user agent when downloading jars with local_jar * Fix mock * Fix typo
1 parent d78d004 commit 6b9d534

9 files changed

Lines changed: 76 additions & 24 deletions

File tree

sdks/python/apache_beam/options/pipeline_options.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1716,6 +1716,12 @@ def _add_argparse_args(cls, parser):
17161716
help=(
17171717
'Docker registry url to use for tagging and pushing the prebuilt '
17181718
'sdk worker container image.'))
1719+
parser.add_argument(
1720+
'--user_agent',
1721+
default=None,
1722+
help=(
1723+
'A user agent string describing the pipeline to external services. '
1724+
'The format should follow RFC2616.'))
17191725

17201726
def validate(self, validator):
17211727
errors = []

sdks/python/apache_beam/runners/portability/flink_uber_jar_job_server.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ def __init__(self, master_url, options):
4848
self._executable_jar = (
4949
options.view_as(
5050
pipeline_options.FlinkRunnerOptions).flink_job_server_jar)
51+
self._user_agent = options.view_as(pipeline_options.SetupOptions).user_agent
5152
self._artifact_port = (
5253
options.view_as(pipeline_options.JobServerOptions).artifact_port)
5354
self._temp_dir = tempfile.mkdtemp(prefix='apache-beam-flink')
@@ -77,7 +78,8 @@ def executable_jar(self):
7778
else:
7879
url = job_server.JavaJarJobServer.path_to_beam_jar(
7980
':runners:flink:%s:job-server:shadowJar' % self.flink_version())
80-
return job_server.JavaJarJobServer.local_jar(url)
81+
return job_server.JavaJarJobServer.local_jar(
82+
url, user_agent=self._user_agent)
8183

8284
def flink_version(self):
8385
full_version = requests.get(

sdks/python/apache_beam/runners/portability/job_server.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,8 +155,9 @@ def path_to_beam_jar(gradle_target, artifact_id=None):
155155
gradle_target, artifact_id=artifact_id)
156156

157157
@staticmethod
158-
def local_jar(url, jar_cache_dir=None):
159-
return subprocess_server.JavaJarServer.local_jar(url, jar_cache_dir)
158+
def local_jar(url, jar_cache_dir=None, user_agent=None):
159+
return subprocess_server.JavaJarServer.local_jar(
160+
url, jar_cache_dir, user_agent)
160161

161162
def subprocess_cmd_and_endpoint(self):
162163
jar_path = self.local_jar(self.path_to_jar(), self._jar_cache_dir)

sdks/python/apache_beam/runners/portability/spark_uber_jar_job_server.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ def __init__(self, rest_url, options):
5353
spark_options = options.view_as(pipeline_options.SparkRunnerOptions)
5454
self._executable_jar = spark_options.spark_job_server_jar
5555
self._spark_version = spark_options.spark_version
56+
self._user_agent = options.view_as(pipeline_options.SetupOptions).user_agent
5657

5758
def start(self):
5859
return self
@@ -78,7 +79,8 @@ def executable_jar(self):
7879
else:
7980
url = job_server.JavaJarJobServer.path_to_beam_jar(
8081
':runners:spark:3:job-server:shadowJar')
81-
return job_server.JavaJarJobServer.local_jar(url)
82+
return job_server.JavaJarJobServer.local_jar(
83+
url, user_agent=self._user_agent)
8284

8385
def create_beam_job(self, job_id, job_name, pipeline, options):
8486
return SparkBeamJob(

sdks/python/apache_beam/transforms/external.py

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535

3636
from apache_beam import pvalue
3737
from apache_beam.coders import RowCoder
38-
from apache_beam.options.pipeline_options import CrossLanguageOptions
38+
from apache_beam.options import pipeline_options
3939
from apache_beam.portability import common_urns
4040
from apache_beam.portability.api import beam_artifact_api_pb2_grpc
4141
from apache_beam.portability.api import beam_expansion_api_pb2
@@ -1030,22 +1030,29 @@ class JavaJarExpansionService(object):
10301030
append_args: arguments to be provided when starting up the
10311031
expansion service using the jar file. These arguments will be appended to
10321032
the default arguments.
1033+
user_agent: the user agent to use when downloading the jar.
10331034
"""
10341035
def __init__(
1035-
self, path_to_jar, extra_args=None, classpath=None, append_args=None):
1036+
self,
1037+
path_to_jar,
1038+
extra_args=None,
1039+
classpath=None,
1040+
append_args=None,
1041+
user_agent=None):
10361042
if extra_args and append_args:
10371043
raise ValueError('Only one of extra_args or append_args may be provided')
10381044
self.path_to_jar = path_to_jar
10391045
self._extra_args = extra_args
10401046
self._classpath = classpath or []
10411047
self._service_count = 0
10421048
self._append_args = append_args or []
1049+
self._user_agent = user_agent
10431050

10441051
def is_existing_service(self):
10451052
return subprocess_server.is_service_endpoint(self.path_to_jar)
10461053

10471054
@staticmethod
1048-
def _expand_jars(jar):
1055+
def _expand_jars(jar, user_agent=None):
10491056
if glob.glob(jar):
10501057
return glob.glob(jar)
10511058
elif isinstance(jar, str) and (jar.startswith('http://') or
@@ -1064,14 +1071,15 @@ def _expand_jars(jar):
10641071
return [jar]
10651072
path = subprocess_server.JavaJarServer.local_jar(
10661073
subprocess_server.JavaJarServer.path_to_maven_jar(
1067-
artifact_id, group_id, version))
1074+
artifact_id, group_id, version),
1075+
user_agent=user_agent)
10681076
return [path]
10691077

10701078
def _default_args(self):
10711079
"""Default arguments to be used by `JavaJarExpansionService`."""
10721080

10731081
to_stage = ','.join([self.path_to_jar] + sum((
1074-
JavaJarExpansionService._expand_jars(jar)
1082+
JavaJarExpansionService._expand_jars(jar, self._user_agent)
10751083
for jar in self._classpath or []), []))
10761084
args = ['{{PORT}}', f'--filesToStage={to_stage}']
10771085
# TODO(robertwb): See if it's possible to scope this per pipeline.
@@ -1080,10 +1088,14 @@ def _default_args(self):
10801088
args.append('--alsoStartLoopbackWorker')
10811089
return args
10821090

1091+
def with_user_agent(self, user_agent: str):
1092+
self._user_agent = user_agent
1093+
return self
1094+
10831095
def __enter__(self):
10841096
if self._service_count == 0:
10851097
self.path_to_jar = subprocess_server.JavaJarServer.local_jar(
1086-
self.path_to_jar)
1098+
self.path_to_jar, user_agent=self._user_agent)
10871099
if self._extra_args is None:
10881100
self._extra_args = self._default_args() + self._append_args
10891101
# Consider memoizing these servers (with some timeout).
@@ -1095,7 +1107,8 @@ def __enter__(self):
10951107
classpath_urls = [
10961108
subprocess_server.JavaJarServer.local_jar(path)
10971109
for jar in self._classpath
1098-
for path in JavaJarExpansionService._expand_jars(jar)
1110+
for path in JavaJarExpansionService._expand_jars(
1111+
jar, user_agent=self._user_agent)
10991112
]
11001113
self._service_provider = subprocess_server.JavaJarServer(
11011114
ExpansionAndArtifactRetrievalStub,
@@ -1138,12 +1151,17 @@ def __init__(
11381151
extra_args=None,
11391152
gradle_appendix=None,
11401153
classpath=None,
1141-
append_args=None):
1154+
append_args=None,
1155+
user_agent=None):
11421156
path_to_jar = subprocess_server.JavaJarServer.path_to_beam_jar(
11431157
gradle_target, gradle_appendix)
11441158
self.gradle_target = gradle_target
11451159
super().__init__(
1146-
path_to_jar, extra_args, classpath=classpath, append_args=append_args)
1160+
path_to_jar,
1161+
extra_args,
1162+
classpath=classpath,
1163+
append_args=append_args,
1164+
user_agent=user_agent)
11471165

11481166

11491167
def _maybe_use_transform_service(provided_service=None, options=None):
@@ -1185,10 +1203,11 @@ def is_docker_available():
11851203
docker_available = is_docker_available()
11861204

11871205
use_transform_service = options.view_as(
1188-
CrossLanguageOptions).use_transform_service
1206+
pipeline_options.CrossLanguageOptions).use_transform_service
1207+
user_agent = options.view_as(pipeline_options.SetupOptions).user_agent
11891208

11901209
if (java_available and provided_service and not use_transform_service):
1191-
return provided_service
1210+
return provided_service.with_user_agent(user_agent)
11921211
elif docker_available:
11931212
if use_transform_service:
11941213
error_append = 'it was explicitly requested'
@@ -1210,7 +1229,7 @@ def is_docker_available():
12101229
beam_version = beam_version.__version__
12111230

12121231
return transform_service_launcher.TransformServiceLauncher(
1213-
project_name, port, beam_version)
1232+
project_name, port, beam_version, user_agent)
12141233
else:
12151234
raise ValueError(
12161235
'Cannot start an expansion service since neither Java nor '

sdks/python/apache_beam/transforms/external_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -829,7 +829,7 @@ def _side_effect_fn(path):
829829

830830
@mock.patch.object(JavaJarServer, 'local_jar')
831831
def test_classpath_with_gradle_artifact(self, local_jar):
832-
def _side_effect_fn(path):
832+
def _side_effect_fn(path, user_agent=None):
833833
return path[path.rindex('/') + 1:]
834834

835835
local_jar.side_effect = _side_effect_fn

sdks/python/apache_beam/utils/subprocess_server.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,13 @@
3434
from typing import Any
3535
from typing import Set
3636
from urllib.error import URLError
37+
from urllib.request import Request
3738
from urllib.request import urlopen
3839

3940
import grpc
4041

4142
from apache_beam.io.filesystems import FileSystems
43+
from apache_beam.runners.internal.names import BEAM_SDK_NAME
4244
from apache_beam.version import __version__ as beam_version
4345

4446
_LOGGER = logging.getLogger(__name__)
@@ -286,6 +288,8 @@ class JavaJarServer(SubprocessServer):
286288
'local', (threading.local, ),
287289
dict(__init__=lambda self: setattr(self, 'replacements', {})))()
288290

291+
_DEFAULT_USER_AGENT = f'{BEAM_SDK_NAME}/{beam_version}'
292+
289293
def __init__(
290294
self,
291295
stub_class,
@@ -416,7 +420,18 @@ def path_to_beam_jar(
416420
artifact_id, cls.BEAM_GROUP_ID, version, maven_repo, appendix=appendix)
417421

418422
@classmethod
419-
def local_jar(cls, url, cache_dir=None):
423+
def local_jar(cls, url, cache_dir=None, user_agent=None):
424+
"""Returns a local path to the given jar, downloading it if necessary.
425+
426+
Args:
427+
url (str): A URL or local path to a jar file.
428+
cache_dir (str): The directory to use for caching downloaded jars. If not
429+
specified, a default temporary directory will be used.
430+
user_agent (str): The user agent to use when downloading the jar.
431+
432+
Returns:
433+
str: The local path to the jar file.
434+
"""
420435
if cache_dir is None:
421436
cache_dir = cls.JAR_CACHE
422437
# TODO: Verify checksum?
@@ -437,7 +452,10 @@ def local_jar(cls, url, cache_dir=None):
437452
try:
438453
url_read = FileSystems.open(url)
439454
except ValueError:
440-
url_read = urlopen(url)
455+
if user_agent is None:
456+
user_agent = cls._DEFAULT_USER_AGENT
457+
url_request = Request(url, headers={'User-Agent': user_agent})
458+
url_read = urlopen(url_request)
441459
with open(cached_jar + '.tmp', 'wb') as jar_write:
442460
shutil.copyfileobj(url_read, jar_write, length=1 << 20)
443461
try:

sdks/python/apache_beam/utils/subprocess_server_test.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,11 @@ class Handler(socketserver.BaseRequestHandler):
108108
timeout = 1
109109

110110
def handle(self):
111-
self.request.recv(1024)
112-
self.request.sendall(b'HTTP/1.1 200 OK\n\ndata')
111+
data = self.request.recv(1024)
112+
if 'User-Agent: Apache Beam SDK for Python' in str(data):
113+
self.request.sendall(b'HTTP/1.1 200 OK\n\ndata')
114+
else:
115+
self.request.sendall(b'HTTP/1.1 400 BAD REQUEST\n\n')
113116

114117
port, = subprocess_server.pick_port(None)
115118
server = socketserver.TCPServer(('localhost', port), Handler)

sdks/python/apache_beam/utils/transform_service_launcher.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,13 @@ class TransformServiceLauncher(object):
5757

5858
# Maintaining a static list of launchers to prevent temporary resources
5959
# from being created unnecessarily.
60-
def __new__(cls, project_name, port, beam_version=None):
60+
def __new__(cls, project_name, port, beam_version=None, user_agent=None):
6161
if project_name not in TransformServiceLauncher._launchers:
6262
TransformServiceLauncher._launchers[project_name] = super(
6363
TransformServiceLauncher, cls).__new__(cls)
6464
return TransformServiceLauncher._launchers[project_name]
6565

66-
def __init__(self, project_name, port, beam_version=None):
66+
def __init__(self, project_name, port, beam_version=None, user_agent=None):
6767
logging.info('Initializing the Beam Transform Service %s.' % project_name)
6868

6969
self._project_name = project_name
@@ -85,7 +85,8 @@ def __init__(self, project_name, port, beam_version=None):
8585
# Get the jar with configs
8686
path_to_local_jar = subprocess_server.JavaJarServer.local_jar(
8787
subprocess_server.JavaJarServer.path_to_beam_jar(
88-
_EXPANSION_SERVICE_LAUNCHER_JAR))
88+
_EXPANSION_SERVICE_LAUNCHER_JAR),
89+
user_agent=user_agent)
8990

9091
with zipfile.ZipFile(path_to_local_jar) as launcher_jar:
9192
launcher_jar.extract('docker-compose.yml', path=temp_dir)

0 commit comments

Comments
 (0)