Skip to content

Fixes #29768: refactor(ingestion): migrate S3 test-connection to declarative framework#29770

Open
IceS2 wants to merge 5 commits into
mainfrom
migrate-s3-testconnection
Open

Fixes #29768: refactor(ingestion): migrate S3 test-connection to declarative framework#29770
IceS2 wants to merge 5 commits into
mainfrom
migrate-s3-testconnection

Conversation

@IceS2

@IceS2 IceS2 commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Fixes #29768

Migrates the S3 storage connector's test-connection off the legacy test_connection_steps helper onto the declarative @check / ChecksProvider / ErrorPack framework. S3 is the first Storage-vertical connector to migrate, so it also seeds the shared scaffolding the remaining storage connectors (gcs, …) will reuse.

What changed

  • New core/connections/test_connection/checks/storage.py (first-occurrence Storage scaffolding, modeled on checks/database.py): a StorageStep enum (ListBuckets, GetMetrics) plus boto3-generic helpers list_buckets / probe_buckets / list_metrics that return Evidence and raise CheckError (carrying the attempted API operation as the command) on failure. command/summary are free text (e.g. s3:ListBuckets) since there is no SQL.
  • s3/connection.py: S3Checks provider with a @check per step, and S3_ERRORS — a botocore ErrorPack matching AWS ClientError codes (InvalidAccessKeyId, SignatureDoesNotMatch, UnrecognizedClientException, ExpiredToken, NoSuchBucket, AccessDenied) and client-side botocore types (NoCredentialsError, EndpointConnectionError), folding in the shared NETWORK_ERRORS pack. The old test_connection override is removed; checks() drives the runner. The client is built lazily inside the checks (an assume-role config calls STS on session creation) so nothing touches the network before the gate.
  • testConnections/storage/s3.json: ListBuckets gains category: ConnectionGate so a failed access step short-circuits.
  • Colocated unit test asserting the checks cover the shipped definition and the error pack classifies each known botocore scenario.

The module-level get_connection(...) -> S3ObjectStoreClient is preserved (the sampler imports it); _get_client delegates to it.

Storage-vertical helpers here are intentionally not extracted from any existing shared code — a later dedicated pass can consolidate across storage connectors as more migrate.

Greptile Summary

This PR migrates the S3 storage connector's test-connection logic from the legacy test_connection_steps helper to the declarative @check / ChecksProvider / ErrorPack framework, and adds the first shared Storage-category scaffolding (checks/storage.py) that future GCS and other storage connectors will reuse.

  • checks/storage.py: New shared Storage helper with StorageStep enum (ListBuckets, GetMetrics) and three helper functions — list_buckets, probe_buckets, list_metrics — that return Evidence and raise CheckError on failure, keeping behavior compatible with S3-compatible stores by using parameter-less boto3 calls.
  • s3/connection.py: Replaces the old test_connection override with S3Checks (a ChecksProvider) carrying S3_ERRORS — a botocore ErrorPack covering nine distinct AWS error codes/types — and a checks() factory that builds the provider lazily to avoid touching STS before the runner gate.
  • s3.json + tests: ListBuckets step gains category: ConnectionGate for proper short-circuit behaviour; the colocated test suite exercises each check path and each error-pack classification.

Confidence Score: 5/5

Safe to merge — the migration is a clean behavioural drop-in of the old test_connection override, the lazy-client pattern is correct, and the existing sampler import path (get_connection) is untouched.

The change is well-scoped: it replaces one method on S3Connection, adds a shared storage helper module, and seeds the JSON definition with the missing category flag. The new checks reproduce the old logic faithfully (probe configured buckets or list all; then list CloudWatch metrics), error classification is comprehensive and tested against all nine botocore failure modes, and the lazy-connect lambda correctly defers STS calls until the runner gate opens. No regressions were found in the sampler path or the base connection lifecycle.

No files require special attention.

Important Files Changed

Filename Overview
ingestion/src/metadata/core/connections/test_connection/checks/storage.py New shared Storage scaffolding: StorageStep enum plus list_buckets, probe_buckets, and list_metrics helpers with proper Evidence/CheckError handling and cap-and-flag logic.
ingestion/src/metadata/ingestion/source/storage/s3/connection.py Replaces legacy test_connection override with S3Checks (ChecksProvider) and S3_ERRORS (ErrorPack); lazy client initialization preserved via connect lambda; get_connection module-level function untouched.
ingestion/tests/unit/source/storage/s3/test_connection.py Comprehensive test suite covering all check paths, error-pack classifications for nine botocore scenarios, caveat conditions, and definition-seed alignment.
openmetadata-service/src/main/resources/json/data/testConnections/storage/s3.json Adds category: ConnectionGate to ListBuckets step so the runner correctly gates on it; both shortCircuit (legacy) and category fields coexist harmlessly.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant UC as User/Automation
    participant BC as BaseConnection.test_connection()
    participant S3C as S3Connection.checks()
    participant S3Ch as S3Checks
    participant TCR as TestConnectionRunner
    participant AWS as AWS (S3 / CloudWatch)

    UC->>BC: test_connection(metadata, workflow)
    BC->>S3C: checks()
    S3C-->>BC: "S3Checks(connect=lambda: self.client, bucket_names=...)"
    BC->>TCR: run(metadata, workflow)
    TCR->>TCR: fetch definition (s3.json steps)
    Note over TCR: Step 1: ListBuckets (ConnectionGate)
    TCR->>S3Ch: check_buckets()
    S3Ch->>AWS: "list_buckets() or list_objects(Bucket, MaxKeys=1)"
    AWS-->>S3Ch: Evidence (buckets enumerated / probed)
    S3Ch-->>TCR: Evidence
    alt ListBuckets failed (gate)
        TCR->>TCR: "gate_failed = True"
        Note over TCR: Step 2: GetMetrics skipped (ConnectionNotEstablished)
    else ListBuckets passed
        Note over TCR: Step 2: GetMetrics (non-mandatory)
        TCR->>S3Ch: get_metrics()
        S3Ch->>AWS: "cloudwatch.list_metrics(Namespace=AWS/S3)"
        AWS-->>S3Ch: Evidence (metrics count)
        S3Ch-->>TCR: Evidence
    end
    TCR-->>BC: TestConnectionResult
    BC->>BC: self.close()
    BC-->>UC: TestConnectionResult
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant UC as User/Automation
    participant BC as BaseConnection.test_connection()
    participant S3C as S3Connection.checks()
    participant S3Ch as S3Checks
    participant TCR as TestConnectionRunner
    participant AWS as AWS (S3 / CloudWatch)

    UC->>BC: test_connection(metadata, workflow)
    BC->>S3C: checks()
    S3C-->>BC: "S3Checks(connect=lambda: self.client, bucket_names=...)"
    BC->>TCR: run(metadata, workflow)
    TCR->>TCR: fetch definition (s3.json steps)
    Note over TCR: Step 1: ListBuckets (ConnectionGate)
    TCR->>S3Ch: check_buckets()
    S3Ch->>AWS: "list_buckets() or list_objects(Bucket, MaxKeys=1)"
    AWS-->>S3Ch: Evidence (buckets enumerated / probed)
    S3Ch-->>TCR: Evidence
    alt ListBuckets failed (gate)
        TCR->>TCR: "gate_failed = True"
        Note over TCR: Step 2: GetMetrics skipped (ConnectionNotEstablished)
    else ListBuckets passed
        Note over TCR: Step 2: GetMetrics (non-mandatory)
        TCR->>S3Ch: get_metrics()
        S3Ch->>AWS: "cloudwatch.list_metrics(Namespace=AWS/S3)"
        AWS-->>S3Ch: Evidence (metrics count)
        S3Ch-->>TCR: Evidence
    end
    TCR-->>BC: TestConnectionResult
    BC->>BC: self.close()
    BC-->>UC: TestConnectionResult
Loading

Reviews (4): Last reviewed commit: "list_buckets: cap count locally instead ..." | Re-trigger Greptile

…arative framework

Migrate the S3 storage connector's test-connection off the legacy
test_connection_steps helper onto the declarative @check / ChecksProvider /
ErrorPack framework, and seed the shared Storage-vertical scaffolding.
@github-actions github-actions Bot added Ingestion safe to test Add this label to run secure Github workflows on PRs labels Jul 6, 2026
Comment thread ingestion/src/metadata/core/connections/test_connection/checks/storage.py Outdated
Comment thread ingestion/src/metadata/ingestion/source/storage/s3/connection.py
@IceS2 IceS2 marked this pull request as ready for review July 6, 2026 13:36
@IceS2 IceS2 requested a review from a team as a code owner July 6, 2026 13:36
Copilot AI review requested due to automatic review settings July 6, 2026 13:36

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Migrates the S3 storage connector’s test-connection implementation from the legacy test_connection_steps flow to the declarative @check / ChecksProvider / ErrorPack framework, and introduces shared storage-vertical check helpers to support future storage connector migrations.

Changes:

  • Added storage-vertical scaffolding (StorageStep + shared boto3-style helpers) under core/connections/test_connection/checks/storage.py.
  • Refactored S3 test-connection to a S3Checks provider with an S3_ERRORS ErrorPack, removing the legacy test_connection override.
  • Updated the shipped S3 test-connection definition and added unit tests to assert step coverage + error classification behavior.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
openmetadata-service/src/main/resources/json/data/testConnections/storage/s3.json Marks ListBuckets as a ConnectionGate category to allow gating/short-circuit behavior.
ingestion/src/metadata/core/connections/test_connection/checks/storage.py Introduces shared storage check steps and boto3-style helper functions returning Evidence / raising CheckError.
ingestion/src/metadata/ingestion/source/storage/s3/connection.py Implements S3Checks + S3_ERRORS and wires S3 connection into the declarative checks runner.
ingestion/tests/unit/source/storage/s3/test_connection.py Adds/updates unit tests to validate S3 checks align with the shipped definition and that error classification works as expected.

Comment thread ingestion/tests/unit/source/storage/s3/test_connection.py
Comment thread ingestion/src/metadata/core/connections/test_connection/checks/storage.py Outdated
Comment on lines +30 to +32
_SEED = (
Path(__file__).parents[6] / "openmetadata-service/src/main/resources/json/data" / "testConnections/storage/s3.json"
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Fragile depth-counted path to the seed JSON

parents[6] encodes the assumption that test_connection.py sits exactly seven directory levels deep from the repo root. If the test file or the ingestion/ tree is ever reorganised, the path resolves silently to the wrong directory (no import error — only a FileNotFoundError when the test actually runs). A common alternative is to walk upward until a sentinel file (e.g., .git or pyproject.toml) is found, or to resolve the path relative to a known package root via importlib.resources.

Copilot AI review requested due to automatic review settings July 6, 2026 14:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

Comment on lines +68 to +83
command = "s3:ListBuckets"
try:
response = client.list_buckets(MaxBuckets=limit) # pyright: ignore[reportAttributeAccessIssue]
except Exception as cause:
raise CheckError(cause, Evidence(command=command)) from cause
buckets = response.get("Buckets", [])
caveat = None
if not buckets:
caveat = Diagnosis(
title="No buckets visible",
remediation="Verify the identity can list buckets, or configure bucketNames explicitly.",
)
summary = f"{_count(len(buckets), 'bucket')} enumerated" + _more_suffix(
len(buckets), bool(response.get("ContinuationToken"))
)
return Evidence(summary=summary, command=command, caveat=caveat)
Comment on lines +60 to +61
Capped at ``limit`` via ``MaxBuckets``; when the account holds more, the
response carries a ``ContinuationToken`` and the summary says so.
Comment on lines +78 to +95
assert evidence.summary == "2 buckets enumerated"
assert evidence.command == "s3:ListBuckets"
assert evidence.caveat is None
client.s3_client.list_buckets.assert_called_once_with(MaxBuckets=100)
client.s3_client.list_objects.assert_not_called()


def test_check_buckets_flags_truncation_beyond_the_cap():
client = MagicMock()
client.s3_client.list_buckets.return_value = {
"Buckets": [{"Name": "a"}],
"ContinuationToken": "more",
}

evidence = _checks(client).check_buckets()

assert "more exist" in evidence.summary

@gitar-bot

gitar-bot Bot commented Jul 6, 2026

Copy link
Copy Markdown
Code Review ✅ Approved 3 resolved / 3 findings

Migrates S3 test-connection to the declarative framework, establishing reusable storage scaffolding and comprehensive error handling. No issues found.

✅ 3 resolved
Edge Case: list_buckets docstring misstates behavior for missing permissions

📄 ingestion/src/metadata/core/connections/test_connection/checks/storage.py:46-60
The list_buckets docstring states that an identity lacking s3:ListAllMyBuckets-style permissions will surface as a non-blocking 'none visible' caveat. In practice, client.list_buckets() returns AccessDenied as a ClientError, which is caught and re-raised as a CheckError — so a permissions failure fails the (ConnectionGate) step rather than producing an empty-listing caveat. The empty-caveat path only triggers for an account that genuinely holds no buckets. Consider tightening the docstring so future maintainers don't assume permission errors are swallowed as caveats.

Quality: InvalidClientTokenId diagnosis has no unit test

📄 ingestion/src/metadata/ingestion/source/storage/s3/connection.py:66-69 📄 ingestion/tests/unit/source/storage/s3/test_connection.py:131-145
The S3_ERRORS pack adds a matcher/diagnosis for InvalidClientTokenId (lines 66-69), but unlike every other classified code (InvalidAccessKeyId, SignatureDoesNotMatch, UnrecognizedClientException, ExpiredToken, NoSuchBucket, AccessDenied), it has no corresponding test_error_pack_* case. Since these matchers are string-based and order-sensitive, an untested rule could silently regress (e.g. be shadowed or removed) without test failure. Add a test asserting S3_ERRORS.classify(_client_error("InvalidClientTokenId", ...)) returns the expected diagnosis.

Edge Case: list_buckets now sends MaxBuckets, may break S3-compatible stores

📄 ingestion/src/metadata/core/connections/test_connection/checks/storage.py:57 📄 ingestion/src/metadata/core/connections/test_connection/checks/storage.py:70 📄 ingestion/src/metadata/core/connections/test_connection/checks/storage.py:80-82 📄 ingestion/src/metadata/ingestion/source/storage/s3/connection.py:88-93 📄 ingestion/src/metadata/ingestion/source/storage/s3/connection.py:103-115
The delta changes client.list_buckets() to client.list_buckets(MaxBuckets=limit) in checks/storage.py (line 70). MaxBuckets/ContinuationToken are a recent addition to the AWS S3 ListBuckets API (introduced late 2024). The S3 connector explicitly supports S3-compatible object stores via endPointURL (MinIO, Ceph RGW, etc. — see connection.py:109 and the error-pack hint at line 90). Some of these servers may not understand the max-buckets query parameter and could reject the request rather than silently ignoring unknown parameters.

Because ListBuckets is now marked category: ConnectionGate, a failure here short-circuits the whole test-connection and reports it as a hard credential/access failure. This means a previously-working S3-compatible endpoint could now fail its connection test purely because of the added MaxBuckets parameter — a behavioral regression relative to the parameter-less list_buckets() it replaced.

Suggested mitigation: verify the pinned boto3 (~=1.41.5) models MaxBuckets (it does for AWS), and consider guarding the capped call so a rejected/unknown-parameter response falls back to an unparameterized list_buckets() rather than failing the gate. At minimum, add a test exercising an S3-compatible client that does not accept MaxBuckets.

Options

Display: compact → Showing less information.

Comment with these commands to change the behavior for this request:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

1 similar comment
@gitar-bot

gitar-bot Bot commented Jul 6, 2026

Copy link
Copy Markdown
Code Review ✅ Approved 3 resolved / 3 findings

Migrates S3 test-connection to the declarative framework, establishing reusable storage scaffolding and comprehensive error handling. No issues found.

✅ 3 resolved
Edge Case: list_buckets docstring misstates behavior for missing permissions

📄 ingestion/src/metadata/core/connections/test_connection/checks/storage.py:46-60
The list_buckets docstring states that an identity lacking s3:ListAllMyBuckets-style permissions will surface as a non-blocking 'none visible' caveat. In practice, client.list_buckets() returns AccessDenied as a ClientError, which is caught and re-raised as a CheckError — so a permissions failure fails the (ConnectionGate) step rather than producing an empty-listing caveat. The empty-caveat path only triggers for an account that genuinely holds no buckets. Consider tightening the docstring so future maintainers don't assume permission errors are swallowed as caveats.

Quality: InvalidClientTokenId diagnosis has no unit test

📄 ingestion/src/metadata/ingestion/source/storage/s3/connection.py:66-69 📄 ingestion/tests/unit/source/storage/s3/test_connection.py:131-145
The S3_ERRORS pack adds a matcher/diagnosis for InvalidClientTokenId (lines 66-69), but unlike every other classified code (InvalidAccessKeyId, SignatureDoesNotMatch, UnrecognizedClientException, ExpiredToken, NoSuchBucket, AccessDenied), it has no corresponding test_error_pack_* case. Since these matchers are string-based and order-sensitive, an untested rule could silently regress (e.g. be shadowed or removed) without test failure. Add a test asserting S3_ERRORS.classify(_client_error("InvalidClientTokenId", ...)) returns the expected diagnosis.

Edge Case: list_buckets now sends MaxBuckets, may break S3-compatible stores

📄 ingestion/src/metadata/core/connections/test_connection/checks/storage.py:57 📄 ingestion/src/metadata/core/connections/test_connection/checks/storage.py:70 📄 ingestion/src/metadata/core/connections/test_connection/checks/storage.py:80-82 📄 ingestion/src/metadata/ingestion/source/storage/s3/connection.py:88-93 📄 ingestion/src/metadata/ingestion/source/storage/s3/connection.py:103-115
The delta changes client.list_buckets() to client.list_buckets(MaxBuckets=limit) in checks/storage.py (line 70). MaxBuckets/ContinuationToken are a recent addition to the AWS S3 ListBuckets API (introduced late 2024). The S3 connector explicitly supports S3-compatible object stores via endPointURL (MinIO, Ceph RGW, etc. — see connection.py:109 and the error-pack hint at line 90). Some of these servers may not understand the max-buckets query parameter and could reject the request rather than silently ignoring unknown parameters.

Because ListBuckets is now marked category: ConnectionGate, a failure here short-circuits the whole test-connection and reports it as a hard credential/access failure. This means a previously-working S3-compatible endpoint could now fail its connection test purely because of the added MaxBuckets parameter — a behavioral regression relative to the parameter-less list_buckets() it replaced.

Suggested mitigation: verify the pinned boto3 (~=1.41.5) models MaxBuckets (it does for AWS), and consider guarding the capped call so a rejected/unknown-parameter response falls back to an unparameterized list_buckets() rather than failing the gate. At minimum, add a test exercising an S3-compatible client that does not accept MaxBuckets.

Options

Display: compact → Showing less information.

Comment with these commands to change the behavior for this request:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

@sonarqubecloud

sonarqubecloud Bot commented Jul 6, 2026

Copy link
Copy Markdown

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

🔴 Playwright Results — 2 failure(s), 21 flaky

✅ 4497 passed · ❌ 2 failed · 🟡 21 flaky · ⏭️ 37 skipped

Shard Passed Failed Flaky Skipped
🔴 Shard 1 434 1 7 16
🟡 Shard 2 819 0 5 8
🟡 Shard 3 799 0 2 7
🟡 Shard 4 815 0 1 5
🔴 Shard 5 861 1 1 0
🟡 Shard 6 769 0 5 1

Genuine Failures (failed on all attempts)

Features/ExploreSortOrderFilter.spec.ts › Container (shard 1)
�[31mTest timeout of 180000ms exceeded.�[39m
Pages/ExploreBrowse.spec.ts › service type drill-down disables unrelated roots and query-panel Clear resets it (shard 5)
�[31mTest timeout of 180000ms exceeded.�[39m
🟡 21 flaky test(s) (passed on retry)
  • Features/Glossary/GlossaryPagination.spec.ts › should filter by InReview status (shard 1, 1 retry)
  • Pages/Bots.spec.ts › Bots Page should work properly (shard 1, 1 retry)
  • Pages/Lineage/DataAssetLineage.spec.ts › verify create lineage for entity - Topic (shard 1, 1 retry)
  • Pages/Lineage/DataAssetLineage.spec.ts › verify create lineage for entity - Search Index (shard 1, 1 retry)
  • Pages/Lineage/DataAssetLineage.spec.ts › verify create lineage for entity - File (shard 1, 1 retry)
  • Pages/Lineage/LineageRightPanel.spec.ts › Verify custom properties tab IS visible for supported type: metric (shard 1, 1 retry)
  • Flow/SearchRBAC.spec.ts › the browse tree only shows the asset-type categories a user can access (shard 1, 1 retry)
  • Features/BulkEditEntity.spec.ts › Glossary Term (Nested) (shard 2, 1 retry)
  • Features/BulkImport.spec.ts › Database Schema (shard 2, 1 retry)
  • Features/ContextCenterPermission.spec.ts › user with editAll permission can see restore action but not delete action on an archived document, and can restore it (shard 2, 1 retry)
  • Features/DataQuality/CertificationFilter.spec.ts › Certification filter narrows both table- and testCase-index queries via the flat field path (shard 2, 1 retry)
  • Features/GlobalPageSize.spec.ts › Page size should persist across different pages (shard 2, 1 retry)
  • Features/RestoreEntityInheritedFields.spec.ts › Validate restore with Inherited domain and data products assigned (shard 3, 1 retry)
  • Features/Tasks/TaskNavigation.spec.ts › navigating to /table/TASK-XXXXX should show 404 (invalid URL pattern) (shard 3, 1 retry)
  • Pages/CustomProperties.spec.ts › Should display custom properties for apiCollection in right panel (shard 4, 1 retry)
  • Pages/ExplorePageRightPanel.spec.ts › Should verify deleted user not visible in owner selection for pipeline (shard 5, 1 retry)
  • Pages/ExplorePageRightPanel.spec.ts › Should allow Data Steward to edit glossary terms for searchIndex (shard 6, 1 retry)
  • Pages/GlossaryImportExport.spec.ts › Glossary Bulk Import Export (shard 6, 1 retry)
  • Pages/GlossaryImportExport.spec.ts › Import partial success - some terms pass, some fail (shard 6, 1 retry)
  • Pages/Lineage/LineageFilters.spec.ts › Verify lineage schema filter selection (shard 6, 1 retry)
  • Pages/Lineage/LineageRightPanel.spec.ts › Verify custom properties tab is NOT visible for pipelineService in platform lineage (shard 6, 1 retry)

📦 Download artifacts

How to debug locally
# Download playwright-test-results-<shard> artifact and unzip
npx playwright show-trace path/to/trace.zip    # view trace

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Ingestion safe to test Add this label to run secure Github workflows on PRs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrate S3 test-connection to the @check framework

2 participants