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

localstack / localstack / 6eb2aab0-9781-40f0-8cc1-9e7c437858cb

22 Apr 2025 04:28PM UTC coverage: 86.272% (+0.007%) from 86.265%
6eb2aab0-9781-40f0-8cc1-9e7c437858cb

push

circleci

web-flow
APIGW: validate REST API custom id tag (#12539)

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

82 existing lines in 10 files now uncovered.

63897 of 74065 relevant lines covered (86.27%)

0.86 hits per line

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

72.22
/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.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
}
26

27

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

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

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

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

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

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

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

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

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

83

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

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

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

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

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

115
        # Build a custom JRE with only the necessary bits to minimise disk footprint
116
        LOG.debug("Optimising JRE installation")
1✔
117
        cmd = (
1✔
118
            "bin/jlink --add-modules "
119
            # Required modules
120
            "java.base,java.desktop,java.instrument,java.management,"
121
            "java.naming,java.scripting,java.sql,java.xml,jdk.compiler,"
122
            # jdk.unsupported contains sun.misc.Unsafe which is required by some dependencies
123
            "jdk.unsupported,"
124
            # Additional cipher suites
125
            "jdk.crypto.cryptoki,"
126
            # Archive support
127
            "jdk.zipfs,"
128
            # Required by MQ broker
129
            "jdk.httpserver,jdk.management,jdk.management.agent,"
130
            # Required by Spark and Hadoop
131
            "java.security.jgss,jdk.security.auth,"
132
            # Include required locales
133
            "jdk.localedata --include-locales en "
134
            # Supplementary args
135
            "--compress 2 --strip-debug --no-header-files --no-man-pages "
136
            # Output directory
137
            "--output " + minimal_jre_path
138
        )
139
        run(cmd, cwd=target_directory)
1✔
140

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

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

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

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

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

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

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

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

193

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

198
    def get_versions(self) -> List[str]:
1✔
199
        return list(JAVA_VERSIONS.keys())
1✔
200

201
    def _get_installer(self, version: str) -> JavaPackageInstaller:
1✔
202
        return JavaPackageInstaller(version)
1✔
203

204

205
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