• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

localstack / localstack / 20154439467

11 Dec 2025 04:58PM UTC coverage: 86.873% (+0.006%) from 86.867%
20154439467

push

github

web-flow
SQS: Improve update support for CloudFormation handlers. (#13477)

34 of 34 new or added lines in 4 files covered. (100.0%)

15 existing lines in 5 files now uncovered.

69932 of 80499 relevant lines covered (86.87%)

0.87 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

72.34
/localstack-core/localstack/packages/java.py
1
import logging
1✔
2
import os
1✔
3

4
import requests
1✔
5

6
from localstack.constants import USER_AGENT_STRING
1✔
7
from localstack.packages import InstallTarget, Package
1✔
8
from localstack.packages.core import ArchiveDownloadAndExtractInstaller
1✔
9
from localstack.utils.files import rm_rf
1✔
10
from localstack.utils.http import get_proxies
1✔
11
from localstack.utils.platform import Arch, get_arch, is_linux, is_mac_os
1✔
12
from localstack.utils.run import run
1✔
13

14
LOG = logging.getLogger(__name__)
1✔
15

16
# Default version if not specified
17
DEFAULT_JAVA_VERSION = "11"
1✔
18

19
# Supported Java LTS versions mapped with Eclipse Temurin build semvers
20
JAVA_VERSIONS = {
1✔
21
    "8": "8u432-b06",
22
    "11": "11.0.25+9",
23
    "17": "17.0.13+11",
24
    "21": "21.0.5+11",
25
    "24": "24.0.1+9",
26
}
27

28

29
class JavaInstallerMixin:
1✔
30
    """
31
    Mixin class for packages that depend on Java. It introduces methods that install Java and help build environment.
32
    """
33

34
    def _prepare_installation(self, target: InstallTarget) -> None:
1✔
35
        java_package.install(target=target)
×
36

37
    def get_java_home(self) -> str | None:
1✔
38
        """
39
        Returns path to JRE installation.
40
        """
41
        return java_package.get_installer().get_java_home()
×
42

43
    def get_java_lib_path(self) -> str | None:
1✔
44
        """
45
        Returns the path to the Java shared library.
46
        """
47
        if java_home := self.get_java_home():
1✔
48
            if is_mac_os():
1✔
49
                return os.path.join(java_home, "lib", "jli", "libjli.dylib")
×
50
            return os.path.join(java_home, "lib", "server", "libjvm.so")
1✔
51
        return None
×
52

53
    def get_java_env_vars(
1✔
54
        self, path: str | None = None, ld_library_path: str | None = None
55
    ) -> dict[str, str]:
56
        """
57
        Returns environment variables pointing to the Java installation. This is useful to build the environment where
58
        the application will run.
59

60
        :param path: If not specified, the value of PATH will be obtained from the environment
61
        :param ld_library_path: If not specified, the value of LD_LIBRARY_PATH will be obtained from the environment
62
        :return: dict consisting of two items:
63
            - JAVA_HOME: path to JRE installation
64
            - PATH: the env path variable updated with JRE bin path
65
        """
66
        java_home = self.get_java_home()
×
67
        java_bin = f"{java_home}/bin"
×
68

69
        path = path or os.environ["PATH"]
×
70

71
        library_path = ld_library_path or os.environ.get("LD_LIBRARY_PATH")
×
72
        # null paths (e.g. `:/foo`) have a special meaning according to the manpages
73
        if library_path is None:
×
74
            full_library_path = f"{java_home}/lib:{java_home}/lib/server"
×
75
        else:
76
            full_library_path = f"{java_home}/lib:{java_home}/lib/server:{library_path}"
×
77

78
        return {
×
79
            "JAVA_HOME": java_home,  # type: ignore[dict-item]
80
            "LD_LIBRARY_PATH": full_library_path,
81
            "PATH": f"{java_bin}:{path}",
82
        }
83

84

85
class JavaPackageInstaller(ArchiveDownloadAndExtractInstaller):
1✔
86
    def __init__(self, version: str):
1✔
87
        super().__init__("java", version, extract_single_directory=True)
1✔
88

89
    def _get_install_marker_path(self, install_dir: str) -> str:
1✔
90
        if is_mac_os():
1✔
91
            return os.path.join(install_dir, "Contents", "Home", "bin", "java")
×
92
        return os.path.join(install_dir, "bin", "java")
1✔
93

94
    def _get_download_url(self) -> str:
1✔
95
        # Note: Eclipse Temurin does not provide Mac aarch64 Java 8 builds.
96
        # See https://adoptium.net/en-GB/supported-platforms/
97
        try:
1✔
98
            LOG.debug("Determining the latest Java build version")
1✔
99
            return self._download_url_latest_release()
1✔
UNCOV
100
        except Exception as exc:  # noqa
×
UNCOV
101
            LOG.debug(
×
102
                "Unable to determine the latest Java build version. Using pinned versions: %s", exc
103
            )
UNCOV
104
            return self._download_url_fallback()
×
105

106
    def _post_process(self, target: InstallTarget) -> None:
1✔
107
        target_directory = self._get_install_dir(target)
1✔
108
        minimal_jre_path = os.path.join(target.value, self.name, f"{self.version}.minimal")
1✔
109
        rm_rf(minimal_jre_path)
1✔
110

111
        # If jlink is not available, use the environment as is
112
        if not os.path.exists(os.path.join(target_directory, "bin", "jlink")):
1✔
113
            LOG.warning("Skipping JRE optimisation because jlink is not available")
×
114
            return
×
115

116
        # Build a custom JRE with only the necessary bits to minimise disk footprint
117
        LOG.debug("Optimising JRE installation")
1✔
118

119
        base_modules = [
1✔
120
            # Required modules
121
            "java.base",
122
            "java.desktop",
123
            "java.instrument",
124
            "java.management",
125
            "java.naming",
126
            "java.scripting",
127
            "java.sql",
128
            "java.xml",
129
            "jdk.compiler",
130
            # jdk.unsupported contains sun.misc.Unsafe which is required by some dependencies
131
            "jdk.unsupported",
132
            # Additional cipher suites
133
            "jdk.crypto.cryptoki",
134
            # Archive support
135
            "jdk.zipfs",
136
            # Required by MQ broker
137
            "jdk.httpserver",
138
            "jdk.management",
139
            "jdk.management.agent",
140
            # Required by Spark and Hadoop
141
            "java.security.jgss",
142
            "jdk.security.auth",
143
            # Include required locales
144
            "jdk.localedata",
145
        ]
146

147
        # Add version-specific modules, not all versions require/support the same set
148
        version_specific_modules = {
1✔
149
            "24": ["jdk.incubator.vector"],  # Required for Trino latest version
150
        }
151

152
        modules = base_modules + version_specific_modules.get(self.version, [])
1✔
153
        modules_str = ",".join(modules)
1✔
154

155
        cmd = (
1✔
156
            "bin/jlink "
157
            # Add modules
158
            f"--add-modules {modules_str} "
159
            # Include required locales
160
            "--include-locales en "
161
            # Supplementary args
162
            "--compress 2 --strip-debug --no-header-files --no-man-pages "
163
            # Output directory
164
            "--output " + minimal_jre_path
165
        )
166
        run(cmd, cwd=target_directory)
1✔
167

168
        rm_rf(target_directory)
1✔
169
        os.rename(minimal_jre_path, target_directory)
1✔
170

171
    def get_java_home(self) -> str | None:
1✔
172
        """
173
        Get JAVA_HOME for this installation of Java.
174
        """
175
        installed_dir = self.get_installed_dir()
1✔
176
        if is_mac_os():
1✔
177
            return os.path.join(installed_dir, "Contents", "Home")  # type: ignore[arg-type]
×
178
        return installed_dir
1✔
179

180
    @property
1✔
181
    def arch(self) -> str | None:
1✔
182
        return (
1✔
183
            "x64" if get_arch() == Arch.amd64 else "aarch64" if get_arch() == Arch.arm64 else None
184
        )
185

186
    @property
1✔
187
    def os_name(self) -> str | None:
1✔
188
        return "linux" if is_linux() else "mac" if is_mac_os() else None
1✔
189

190
    def _download_url_latest_release(self) -> str:
1✔
191
        """
192
        Return the download URL for latest stable JDK build.
193
        """
194
        endpoint = (
1✔
195
            f"https://api.adoptium.net/v3/assets/latest/{self.version}/hotspot?"
196
            f"os={self.os_name}&architecture={self.arch}&image_type=jdk"
197
        )
198
        # Override user-agent because Adoptium API denies service to `requests` library
199
        response = requests.get(
1✔
200
            endpoint, headers={"user-agent": USER_AGENT_STRING}, proxies=get_proxies()
201
        ).json()
202
        return response[0]["binary"]["package"]["link"]
1✔
203

204
    def _download_url_fallback(self) -> str:
1✔
205
        """
206
        Return the download URL for pinned JDK build.
207
        """
UNCOV
208
        semver = JAVA_VERSIONS[self.version]
×
UNCOV
209
        tag_slug = f"jdk-{semver}"
×
UNCOV
210
        semver_safe = semver.replace("+", "_")
×
211

212
        # v8 uses a different tag and version scheme
UNCOV
213
        if self.version == "8":
×
214
            semver_safe = semver_safe.replace("-", "")
×
215
            tag_slug = f"jdk{semver}"
×
216

UNCOV
217
        return (
×
218
            f"https://github.com/adoptium/temurin{self.version}-binaries/releases/download/{tag_slug}/"
219
            f"OpenJDK{self.version}U-jdk_{self.arch}_{self.os_name}_hotspot_{semver_safe}.tar.gz"
220
        )
221

222

223
class JavaPackage(Package[JavaPackageInstaller]):
1✔
224
    def __init__(self, default_version: str = DEFAULT_JAVA_VERSION):
1✔
225
        super().__init__(name="Java", default_version=default_version)
1✔
226

227
    def get_versions(self) -> list[str]:
1✔
228
        return list(JAVA_VERSIONS.keys())
1✔
229

230
    def _get_installer(self, version: str) -> JavaPackageInstaller:
1✔
231
        return JavaPackageInstaller(version)
1✔
232

233

234
java_package = JavaPackage()
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc