Region
Centralized region discovery and routing service.
Architecture
We provide a composable suite of different micro-services that provide different functionality.
Hardware provisioning can come in a number of different flavors, namely bare-metal, managed Kubernetes etc.
These services have a common requirement on a compute cloud/region to provision projects, users, roles, networking etc. in order to function.
A Note on Security
At present this region controller is monolithic, offering region discovery and routing to allow scoped provisioning and deprovisioning or the aforementioned hardware prerequisites.
Given this service holds elevated privilege credentials to all of those clouds, it make it somewhat of a honey pot.
Eventually, the goal is to have this act as a purely discovery and routing service, and platform specific region controllers live in those platforms, including their credentials.
The end goal being the compromise of one, doesn't affect the others, limiting blast radius, and not having to disseminate credentials across the internet, they would reside locally in the cloud platform's AS to improve security guarantees.
Supported Providers
OpenStack
OpenStack is an open source cloud provider that allows on premise provisioning of virtual and physical infrastructure.
It allows a vertically integrated stack from server to application, so you have full control over the platform.
This obviously entails a support crew to keep it up and running!
For further info see the OpenStack provider documentation.
Kubernetes
Kubernetes regions allow Kubernetes clusters from any cloud provider to be consumed and increase capacity without the hassle of physical infrastructure.
Kubernetes regions are exposed to end users with virtual Kubernetes clusters.
For further info see the Kubernetes provider documentation.
Installation
Prerequisites
The use the Kubernetes service you first need to install:
Installing the Service
The region service is typically installed with Helm as follows:
region:
ingress:
host: region.unikorn-cloud.org
clusterIssuer: letsencrypt-production
externalDns: true
oidc:
issuer: https://identity.unikorn-cloud.org
regions:
- name: gb-north-1
provider: openstack
openstack:
endpoint: https://my-openstack-endpoint.com:5000
serviceAccountSecret:
namespace: unikorn-region
name: gb-north-1-credentials # See the provider setup section
The configures the service to be exposed on the specified host using an ingress with TLS and DDNS.
The OIDC configuration allows token validation at the API.
Regions define cloud instances to expose to clients.
Running Tests
Local Testing
-
Set up your environment configuration:
Copy the example config and update with your values:
cp test/.env.example test/.env
Or create environment-specific files (not tracked in git):
# Create .env.dev with your dev credentials
cp test/.env.example test/.env.dev
# Edit test/.env.dev with dev values
# Create .env.uat with your UAT credentials
cp test/.env.example test/.env.uat
# Edit test/.env.uat with UAT values
# Use the appropriate environment
cp test/.env.dev test/.env # For dev environment
cp test/.env.uat test/.env # For UAT environment
-
Configure the required values in test/.env:
API_BASE_URL - Region API server URL
API_AUTH_TOKEN - Service token from console
TEST_ORG_ID, TEST_PROJECT_ID, TEST_REGION_ID - Test data IDs
-
Run tests:
make test-api # Run all tests
make test-api-verbose # Verbose output
make test-api-focus FOCUS="should return all available" # Run focused tests
Note: The .env, .env.dev, and .env.uat files are gitignored and contain sensitive credentials. They should never be committed to the repository.
GitHub Actions
Trigger the workflow manually from the Actions tab:
- Go to Actions → API Tests
- Click Run workflow
- Check which environments to test:
- Run Dev tests (checked by default)
- Run UAT tests (unchecked by default)
- Can run one, both, or neither
- View results in the workflow run and download test artifacts
Contract Testing
Contract tests verify that the provider service meets consumer expectations defined in the Pact Broker.
Prerequisites
-
Install Pact FFI library (macOS):
brew tap pact-foundation/pact-ruby-standalone
brew install pact-ruby-standalone
mkdir -p $HOME/Library/pact
cp /usr/local/opt/pact-ruby-standalone/libexec/lib/*.dylib $HOME/Library/pact/
-
Start Pact Broker (optional, for local testing):
Download the Uni-core repo and run the following command from its root dir:
make pact-broker-start
Running Consumer Contract Tests
Run consumer tests locally:
make test-contracts-consumer
Run with verbose output:
make test-contracts-consumer-verbose
Publish consumer pact files to Pact Broker (requires Docker):
make publish-contracts-consumer
Run consumer tests and publish in CI:
make test-contracts-consumer-ci
Check if a version can be safely deployed:
make can-i-deploy
Record a deployment to an environment:
make record-deployment
Running Provider Contract Tests
Run verification against pacts from the Pact Broker (this assumes you have already run and published the consumer tests to the broker):
make test-contracts-provider
Run verification against a local pact file (pact for the consumer when testing without a broker):
make test-contracts-provider-local PACT_FILE=/path/to/pact.json
Run with verbose output:
make test-contracts-provider-verbose
Automated Provider Verification (Webhook)
The repository includes a webhook-triggered workflow (.github/workflows/pact-verification.yaml) that automatically verifies contracts when consumers publish new pacts.
How it works:
- Consumer (e.g., uni-compute) publishes a new pact to Pact Broker
- Pact Broker webhook triggers this repository's GitHub Actions workflow
- Provider verification runs automatically against the new contract
- Results are published back to Pact Broker
- Consumer's
can-i-deploy check can now validate compatibility
Setup:
The webhook is configured in the Pact Broker by the consumer service. See uni-compute's README for webhook setup instructions.
Workflow trigger:
on:
repository_dispatch:
types: [pact_verification]
This workflow receives metadata about which pact to verify and runs make test-contracts-provider-ci to verify and publish results.
Writing Consumer Tests
Consumer tests define uni-region's expectations when calling external APIs (like uni-identity). Tests are located in test/contracts/consumer/{provider}/.
Structure:
suite_test.go - Ginkgo test suite setup
{feature}_test.go - Consumer contract tests for specific features (e.g., rbac_test.go, allocations_test.go)
Basic Pattern:
-
Test Setup:
mockProvider, err := consumer.NewV2Pact(consumer.MockHTTPProviderConfig{
Consumer: "uni-region",
Provider: "uni-identity",
PactDir: "./pacts",
})
-
Define Interactions:
err := mockProvider.
AddInteraction().
Given("organization exists with global read permission").
UponReceiving("a request to get organization ACL").
WithRequest(http.MethodGet, "/api/v1/organizations/test-org/acl").
WillRespondWith(http.StatusOK, func(b *consumer.V2ResponseBuilder) {
b.JSONBody(matchers.StructMatcher{
"scopes": matchers.EachLike(map[string]interface{}{
"name": matchers.String("global"),
// ... more fields
}, 1),
})
}).
ExecuteTest(nil, func(config consumer.MockServerConfig) error {
// Execute actual API call here
return nil
})
-
Test Organization:
- Group related tests by feature (RBAC, allocations, etc.)
- Use descriptive "Given", "UponReceiving" phrases
- Test both success and error scenarios
- Use Pact matchers for flexible matching
Example: See test/contracts/consumer/identity/ for complete examples of RBAC and allocation consumer tests.
Writing Provider Tests
Provider tests are located in test/contracts/provider/{consumer}/. Each consumer has:
verify_test.go - Main test setup and verification
states.go - State handlers for setting up test data
middleware.go - Test-specific middleware (e.g., mock ACL)
Basic Pattern:
-
Test Structure (verify_test.go):
- Uses Ginkgo/Gomega for BDD-style tests
- Starts a test server in
BeforeEach
- Creates state handlers mapping Pact states to setup functions
- Runs verification using
provider.NewVerifier()
-
State Handlers (states.go):
- Implement parameterized state handlers that accept organization ID and other parameters
- Use
StateManager to create/cleanup Kubernetes resources
- Follow the builder pattern for creating test resources (see
RegionBuilder)
-
Example State Handler:
func (sm *StateManager) HandleOrganizationState(ctx context.Context, setup bool, params map[string]interface{}) error {
orgID := getStringParam(params, ParamOrganizationID, "test-org")
regionType := getStringParam(params, ParamRegionType, "")
if setup {
return sm.setupRegions(ctx, orgID, regionType)
}
return sm.cleanupAllRegions(ctx)
}
-
State Constants:
- Define state names as constants (must match consumer contract states)
- Use parameter keys for passing data to state handlers
See test/contracts/provider/compute/ for a complete example following this pattern.
Running All Contract Tests
Run both consumer and provider tests together:
make test-contracts
This is useful for ensuring both your consumer expectations and provider implementations are working correctly before publishing to the Pact Broker.
What Next?
The region controller is useless as it is, and requires a service provider to use it to yield a consumable resource.
Try out the Kubernetes service.