Centralized region discovery and routing service.
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.
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.
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 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.
The use the Kubernetes service you first need to install:
- The identity service to provide API authentication and authorization.
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 sectionThe 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.
-
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 URLAPI_AUTH_TOKEN- Service token from consoleTEST_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.
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 tests verify that the provider service meets consumer expectations defined in the Pact Broker.
-
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-startRun consumer tests locally:
make test-contracts-consumerRun with verbose output:
make test-contracts-consumer-verbosePublish consumer pact files to Pact Broker (requires Docker):
make publish-contracts-consumerRun consumer tests and publish in CI:
make test-contracts-consumer-ciCheck if a version can be safely deployed:
make can-i-deployRecord a deployment to an environment:
make record-deploymentRun verification against pacts from the Pact Broker (this assumes you have already run and published the consumer tests to the broker):
make test-contracts-providerRun 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.jsonRun with verbose output:
make test-contracts-provider-verboseThe 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-deploycheck 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.
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.
Provider tests are located in test/contracts/provider/{consumer}/. Each consumer has:
verify_test.go- Main test setup and verificationstates.go- State handlers for setting up test datamiddleware.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
StateManagerto 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.
Run both consumer and provider tests together:
make test-contractsThis is useful for ensuring both your consumer expectations and provider implementations are working correctly before publishing to the Pact Broker.
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.