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

localstack / localstack / 16712833638

01 Aug 2025 02:00PM UTC coverage: 86.885% (-0.01%) from 86.897%
16712833638

push

github

web-flow
merge CFn V1 and CFnV2 tests (#12944)

Co-authored-by: Simon Walker <simon.walker@localstack.cloud>

3 of 3 new or added lines in 1 file covered. (100.0%)

33 existing lines in 11 files now uncovered.

66476 of 76510 relevant lines covered (86.89%)

0.87 hits per line

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

71.43
/localstack-core/localstack/packages/java.py
1
import logging
1✔
2
import os
1✔
3
from typing import List
1✔
4

5
import requests
1✔
6

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

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

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

20
# Supported Java LTS versions mapped with Eclipse Temurin build semvers
21
JAVA_VERSIONS = {
1✔
22
    "8": "8u432-b06",
23
    "11": "11.0.25+9",
24
    "17": "17.0.13+11",
25
    "21": "21.0.5+11",
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
        """
UNCOV
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✔
100
        except Exception as exc:  # noqa
×
101
            LOG.debug(
×
102
                "Unable to determine the latest Java build version. Using pinned versions: %s", exc
103
            )
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
        cmd = (
1✔
119
            "bin/jlink --add-modules "
120
            # Required modules
121
            "java.base,java.desktop,java.instrument,java.management,"
122
            "java.naming,java.scripting,java.sql,java.xml,jdk.compiler,"
123
            # jdk.unsupported contains sun.misc.Unsafe which is required by some dependencies
124
            "jdk.unsupported,"
125
            # Additional cipher suites
126
            "jdk.crypto.cryptoki,"
127
            # Archive support
128
            "jdk.zipfs,"
129
            # Required by MQ broker
130
            "jdk.httpserver,jdk.management,jdk.management.agent,"
131
            # Required by Spark and Hadoop
132
            "java.security.jgss,jdk.security.auth,"
133
            # Include required locales
134
            "jdk.localedata --include-locales en "
135
            # Supplementary args
136
            "--compress 2 --strip-debug --no-header-files --no-man-pages "
137
            # Output directory
138
            "--output " + minimal_jre_path
139
        )
140
        run(cmd, cwd=target_directory)
1✔
141

142
        rm_rf(target_directory)
1✔
143
        os.rename(minimal_jre_path, target_directory)
1✔
144

145
    def get_java_home(self) -> str | None:
1✔
146
        """
147
        Get JAVA_HOME for this installation of Java.
148
        """
149
        installed_dir = self.get_installed_dir()
1✔
150
        if is_mac_os():
1✔
151
            return os.path.join(installed_dir, "Contents", "Home")  # type: ignore[arg-type]
×
152
        return installed_dir
1✔
153

154
    @property
1✔
155
    def arch(self) -> str | None:
1✔
156
        return (
1✔
157
            "x64" if get_arch() == Arch.amd64 else "aarch64" if get_arch() == Arch.arm64 else None
158
        )
159

160
    @property
1✔
161
    def os_name(self) -> str | None:
1✔
162
        return "linux" if is_linux() else "mac" if is_mac_os() else None
1✔
163

164
    def _download_url_latest_release(self) -> str:
1✔
165
        """
166
        Return the download URL for latest stable JDK build.
167
        """
168
        endpoint = (
1✔
169
            f"https://api.adoptium.net/v3/assets/latest/{self.version}/hotspot?"
170
            f"os={self.os_name}&architecture={self.arch}&image_type=jdk"
171
        )
172
        # Override user-agent because Adoptium API denies service to `requests` library
173
        response = requests.get(
1✔
174
            endpoint, headers={"user-agent": USER_AGENT_STRING}, proxies=get_proxies()
175
        ).json()
176
        return response[0]["binary"]["package"]["link"]
1✔
177

178
    def _download_url_fallback(self) -> str:
1✔
179
        """
180
        Return the download URL for pinned JDK build.
181
        """
182
        semver = JAVA_VERSIONS[self.version]
×
183
        tag_slug = f"jdk-{semver}"
×
184
        semver_safe = semver.replace("+", "_")
×
185

186
        # v8 uses a different tag and version scheme
187
        if self.version == "8":
×
188
            semver_safe = semver_safe.replace("-", "")
×
189
            tag_slug = f"jdk{semver}"
×
190

191
        return (
×
192
            f"https://github.com/adoptium/temurin{self.version}-binaries/releases/download/{tag_slug}/"
193
            f"OpenJDK{self.version}U-jdk_{self.arch}_{self.os_name}_hotspot_{semver_safe}.tar.gz"
194
        )
195

196

197
class JavaPackage(Package[JavaPackageInstaller]):
1✔
198
    def __init__(self, default_version: str = DEFAULT_JAVA_VERSION):
1✔
199
        super().__init__(name="Java", default_version=default_version)
1✔
200

201
    def get_versions(self) -> List[str]:
1✔
202
        return list(JAVA_VERSIONS.keys())
1✔
203

204
    def _get_installer(self, version: str) -> JavaPackageInstaller:
1✔
205
        return JavaPackageInstaller(version)
1✔
206

207

208
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

© 2026 Coveralls, Inc