(
+
+
+ Home Region*
+
+
+
+
+
+ Shown in the OCI Console under Tenancy Details. Used only to
+ validate the credentials: all subscribed regions are scanned.
+
+
+
+ )}
+ />
({
+ value: region,
+ label: region,
+ })),
+ },
+ { heading: "Government", options: OCI_GOVERNMENT_REGIONS },
+];
+
+export const OCI_REGION_VALUES = OCI_REGION_GROUPS.flatMap((group) =>
+ group.options.map((option) => option.value),
+);
diff --git a/ui/tests/providers/providers-page.ts b/ui/tests/providers/providers-page.ts
index ecb8755ccd..9777c1b61b 100644
--- a/ui/tests/providers/providers-page.ts
+++ b/ui/tests/providers/providers-page.ts
@@ -224,6 +224,7 @@ export interface OCIProviderCredential {
userId?: string;
fingerprint?: string;
keyContent?: string;
+ homeRegion?: string;
}
// AlibabaCloud credential options
@@ -365,6 +366,7 @@ export class ProvidersPage extends BasePage {
readonly ociUserIdInput: Locator;
readonly ociFingerprintInput: Locator;
readonly ociKeyContentInput: Locator;
+ readonly ociHomeRegionCombobox: Locator;
// AlibabaCloud provider form elements
readonly alibabacloudAccountIdInput: Locator;
@@ -510,6 +512,9 @@ export class ProvidersPage extends BasePage {
this.ociKeyContentInput = page.getByRole("textbox", {
name: /Private Key Content/i,
});
+ this.ociHomeRegionCombobox = page.getByRole("combobox", {
+ name: /Home Region/i,
+ });
// AlibabaCloud provider form inputs
this.alibabacloudAccountIdInput = page.getByRole("textbox", {
@@ -1284,6 +1289,12 @@ export class ProvidersPage extends BasePage {
if (credentials.keyContent) {
await this.ociKeyContentInput.fill(credentials.keyContent);
}
+ if (credentials.homeRegion) {
+ await this.ociHomeRegionCombobox.click();
+ await this.page
+ .locator(`[role="option"][data-value="${credentials.homeRegion}"]`)
+ .click();
+ }
}
async verifyOCICredentialsPageLoaded(): Promise {
@@ -1294,6 +1305,7 @@ export class ProvidersPage extends BasePage {
await expect(this.ociUserIdInput).toBeVisible();
await expect(this.ociFingerprintInput).toBeVisible();
await expect(this.ociKeyContentInput).toBeVisible();
+ await expect(this.ociHomeRegionCombobox).toBeVisible();
}
async verifyOCIUpdateCredentialsPageLoaded(): Promise {
@@ -1304,6 +1316,7 @@ export class ProvidersPage extends BasePage {
await expect(this.ociUserIdInput).toBeVisible();
await expect(this.ociFingerprintInput).toBeVisible();
await expect(this.ociKeyContentInput).toBeVisible();
+ await expect(this.ociHomeRegionCombobox).toBeVisible();
}
async selectAlibabaCloudProvider(): Promise {
diff --git a/ui/tests/providers/providers.md b/ui/tests/providers/providers.md
index 0bc1b9d47d..049c3fcbe3 100644
--- a/ui/tests/providers/providers.md
+++ b/ui/tests/providers/providers.md
@@ -611,7 +611,7 @@
**Preconditions:**
- Admin user authentication required (admin.auth.setup setup)
-- Environment variables configured: E2E_OCI_TENANCY_ID, E2E_OCI_USER_ID, E2E_OCI_FINGERPRINT, E2E_OCI_KEY_CONTENT
+- Environment variables configured: E2E_OCI_TENANCY_ID, E2E_OCI_USER_ID, E2E_OCI_FINGERPRINT, E2E_OCI_KEY_CONTENT, E2E_OCI_REGION (optional, defaults to us-ashburn-1)
- Remove any existing provider with the same Tenancy ID before starting the test
- This test must be run serially and never in parallel with other tests, as it requires the Tenancy ID not to be already registered beforehand.
@@ -622,7 +622,7 @@
3. Select OCI provider type
4. Fill provider details (tenancy ID and alias)
5. Verify OCI credentials page is loaded
-6. Fill OCI credentials (user ID, fingerprint, key content)
+6. Fill OCI credentials (user ID, fingerprint, key content, home region)
7. Confirm provider connection without launching a scan
8. Verify return to Providers page
9. Verify provider exists in Providers table
@@ -640,7 +640,7 @@
- Connect account page displays OCI option
- Provider details form accepts tenancy ID and alias
- OCI credentials page loads
-- Credentials form accepts all required fields (user ID, fingerprint, key content)
+- Credentials form accepts all required fields (user ID, fingerprint, key content, home region)
- Launch step appears
- Successful return to Providers page after closing the launch step
- Provider exists in Providers table (verified by tenancy ID)
@@ -670,7 +670,7 @@
**Preconditions:**
- Admin user authentication required (admin.auth.setup setup)
-- Environment variables configured: E2E_OCI_TENANCY_ID, E2E_OCI_USER_ID, E2E_OCI_FINGERPRINT, E2E_OCI_KEY_CONTENT
+- Environment variables configured: E2E_OCI_TENANCY_ID, E2E_OCI_USER_ID, E2E_OCI_FINGERPRINT, E2E_OCI_KEY_CONTENT, E2E_OCI_REGION (optional, defaults to us-ashburn-1)
- An OCI provider with the specified Tenancy ID must already exist (run PROVIDER-E2E-012 first)
- This test must be run serially and never in parallel with other tests
@@ -682,7 +682,7 @@
4. Click "Update Credentials" option
5. Verify update credentials page is loaded
6. Verify OCI credentials form fields are visible (confirms providerUid is loaded)
-7. Fill OCI credentials (user ID, fingerprint, key content)
+7. Fill OCI credentials (user ID, fingerprint, key content, home region)
8. Click Next to submit
9. Verify successful navigation to test connection page
diff --git a/ui/tests/providers/providers.spec.ts b/ui/tests/providers/providers.spec.ts
index 4052a1d4c0..4cdd0aa63c 100644
--- a/ui/tests/providers/providers.spec.ts
+++ b/ui/tests/providers/providers.spec.ts
@@ -954,6 +954,7 @@ test.describe("Add Provider", () => {
const userId = process.env.E2E_OCI_USER_ID ?? "";
const fingerprint = process.env.E2E_OCI_FINGERPRINT ?? "";
const keyContent = process.env.E2E_OCI_KEY_CONTENT ?? "";
+ const homeRegion = process.env.E2E_OCI_REGION ?? "us-ashburn-1";
// Setup before each test
test.beforeEach(async ({ page }) => {
@@ -995,6 +996,7 @@ test.describe("Add Provider", () => {
userId: userId,
fingerprint: fingerprint,
keyContent: keyContent,
+ homeRegion: homeRegion,
};
// Navigate to providers page
@@ -1439,6 +1441,7 @@ test.describe("Update Provider Credentials", () => {
const userId = process.env.E2E_OCI_USER_ID ?? "";
const fingerprint = process.env.E2E_OCI_FINGERPRINT ?? "";
const keyContent = process.env.E2E_OCI_KEY_CONTENT ?? "";
+ const homeRegion = process.env.E2E_OCI_REGION ?? "us-ashburn-1";
// Setup before each test
test.beforeEach(async ({ page }) => {
@@ -1465,6 +1468,7 @@ test.describe("Update Provider Credentials", () => {
userId: userId,
fingerprint: fingerprint,
keyContent: keyContent,
+ homeRegion: homeRegion,
};
// Navigate to providers page
diff --git a/ui/types/components.ts b/ui/types/components.ts
index 4053984241..340f0c77d9 100644
--- a/ui/types/components.ts
+++ b/ui/types/components.ts
@@ -281,6 +281,7 @@ export type OCICredentials = {
[ProviderCredentialFields.OCI_FINGERPRINT]: string;
[ProviderCredentialFields.OCI_KEY_CONTENT]: string;
[ProviderCredentialFields.OCI_TENANCY]: string;
+ [ProviderCredentialFields.OCI_REGION]: string;
[ProviderCredentialFields.OCI_PASS_PHRASE]?: string;
[ProviderCredentialFields.PROVIDER_ID]: string;
};
diff --git a/ui/types/env.d.ts b/ui/types/env.d.ts
index b2532f6b95..2fb81bf73e 100644
--- a/ui/types/env.d.ts
+++ b/ui/types/env.d.ts
@@ -151,6 +151,7 @@ declare global {
E2E_OCI_USER_ID?: string;
E2E_OCI_FINGERPRINT?: string;
E2E_OCI_KEY_CONTENT?: string;
+ E2E_OCI_REGION?: string;
// E2E Alibaba Cloud
E2E_ALIBABACLOUD_ACCOUNT_ID?: string;
diff --git a/ui/types/formSchemas.test.ts b/ui/types/formSchemas.test.ts
index ddb5f4e301..8206ed3311 100644
--- a/ui/types/formSchemas.test.ts
+++ b/ui/types/formSchemas.test.ts
@@ -307,11 +307,33 @@ describe("addCredentialsFormSchema - oraclecloud", () => {
[ProviderCredentialFields.OCI_TENANCY]: "ocid1.tenancy.oc1..example",
} as const;
- it("accepts OCI API key credentials without region", () => {
+ it("rejects OCI API key credentials without a home region", () => {
const schema = addCredentialsFormSchema("oraclecloud");
const result = schema.safeParse(BASE_OCI_VALUES);
+ expect(result.success).toBe(false);
+ });
+
+ it("rejects an unknown OCI home region", () => {
+ const schema = addCredentialsFormSchema("oraclecloud");
+
+ const result = schema.safeParse({
+ ...BASE_OCI_VALUES,
+ [ProviderCredentialFields.OCI_REGION]: "mars-north-1",
+ });
+
+ expect(result.success).toBe(false);
+ });
+
+ it("accepts OCI API key credentials with a home region", () => {
+ const schema = addCredentialsFormSchema("oraclecloud");
+
+ const result = schema.safeParse({
+ ...BASE_OCI_VALUES,
+ [ProviderCredentialFields.OCI_REGION]: "me-abudhabi-1",
+ });
+
expect(result.success).toBe(true);
});
});
diff --git a/ui/types/formSchemas.ts b/ui/types/formSchemas.ts
index d34a1c84ef..11c8bef03e 100644
--- a/ui/types/formSchemas.ts
+++ b/ui/types/formSchemas.ts
@@ -1,6 +1,7 @@
import yaml from "js-yaml";
import { z } from "zod";
+import { OCI_REGION_VALUES } from "@/lib/provider-credentials/oci-regions";
import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
import { validateMutelistYaml, validateYaml } from "@/lib/yaml";
import { MAX_SAML_ADDITIONAL_EMAIL_DOMAINS } from "@/types/saml";
@@ -325,6 +326,14 @@ export const addCredentialsFormSchema = (
[ProviderCredentialFields.OCI_TENANCY]: z
.string()
.min(1, "Tenancy OCID is required"),
+ [ProviderCredentialFields.OCI_REGION]: z
+ .string()
+ .refine(
+ (region) => OCI_REGION_VALUES.includes(region),
+ {
+ error: "Home region is required",
+ },
+ ),
[ProviderCredentialFields.OCI_PASS_PHRASE]: z
.union([z.string(), z.literal("")])
.optional(),
diff --git a/util/update_oci_regions.py b/util/update_oci_regions.py
index cb012bec58..acd858a7ec 100644
--- a/util/update_oci_regions.py
+++ b/util/update_oci_regions.py
@@ -178,6 +178,34 @@ def update_config_file(regions, config_file_path):
logging.info(f"Updated OCI_COMMERCIAL_REGIONS with {len(regions)} regions")
+def update_ui_regions_file(regions, ui_file_path):
+ """Rewrite OCI_COMMERCIAL_REGIONS in the UI region list used by the credentials form."""
+ logging.info(f"Updating UI regions file: {ui_file_path}")
+
+ with open(ui_file_path, "r") as f:
+ ui_content = f.read()
+
+ new_regions_array = "const OCI_COMMERCIAL_REGIONS = [\n"
+ for region_id in regions.keys():
+ new_regions_array += f' "{region_id}",\n'
+ new_regions_array += "];"
+
+ pattern = r"const OCI_COMMERCIAL_REGIONS = \[[^\]]*\];"
+ if not re.search(pattern, ui_content):
+ raise Exception(
+ "Validation failed: OCI_COMMERCIAL_REGIONS not found in the UI regions file."
+ )
+ updated_content = re.sub(pattern, new_regions_array, ui_content)
+
+ if updated_content == ui_content:
+ logging.warning("No changes detected in UI regions file")
+ return
+
+ with open(ui_file_path, "w") as f:
+ f.write(updated_content)
+ logging.info("Successfully updated UI regions file")
+
+
def main():
"""
Main execution function for OCI regions updater.
@@ -201,6 +229,16 @@ def main():
update_config_file(commercial_regions, config_file_path)
+ ui_file_path = os.path.join(
+ os.path.dirname(os.path.realpath(__file__)),
+ "..",
+ "ui",
+ "lib",
+ "provider-credentials",
+ "oci-regions.ts",
+ )
+ update_ui_regions_file(commercial_regions, ui_file_path)
+
logging.info("OCI regions update completed successfully")
return 0