Skip to content

Commit 83b0f1b

Browse files
committed
simplify
1 parent bc3314c commit 83b0f1b

2 files changed

Lines changed: 18 additions & 104 deletions

File tree

v1/providers/testkube/README.md

Lines changed: 2 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Test Kubernetes Provider
22

3-
`test-kubernetes` is a developer-only provider that backs cloud instance lifecycle calls with Kubernetes resources.
3+
`test-kubernetes` is a test/non-prod-only provider that backs cloud instance lifecycle calls with Kubernetes resources.
44

55
## Credentials
66

@@ -52,89 +52,4 @@ Clean up:
5252

5353
```bash
5454
minikube --profile testkube delete
55-
```
56-
57-
### CI: kind
58-
59-
kind remains the preferred CI path because it runs a disposable Kubernetes cluster on the GitHub Linux runner's Docker runtime. The CI path still uses `cloud-provider-kind` to provide `LoadBalancer` Services, but the provider itself only reads standard Kubernetes Service status.
60-
61-
Minimal GitHub Actions sketch:
62-
63-
```yaml
64-
jobs:
65-
testkube-validation:
66-
runs-on: ubuntu-latest
67-
steps:
68-
- uses: actions/checkout@v4
69-
- uses: actions/setup-go@v5
70-
with:
71-
go-version-file: go.mod
72-
- name: Install cloud-provider-kind
73-
run: go install sigs.k8s.io/cloud-provider-kind@latest
74-
- name: Create kind cluster
75-
run: |
76-
kind create cluster --name testkube --wait 5m
77-
kubectl label node testkube-control-plane node.kubernetes.io/exclude-from-external-load-balancers- || true
78-
docker build -t ghcr.io/brevdev/cloud/testkube-ubuntu-vm:latest ./v1/providers/testkube/images/ubuntu-vm
79-
kind load docker-image ghcr.io/brevdev/cloud/testkube-ubuntu-vm:latest --name testkube
80-
kubectl create namespace testkube
81-
echo "TESTKUBE_KUBECONFIG_BASE64=$(kind get kubeconfig --name testkube | base64 | tr -d '\n')" >> "$GITHUB_ENV"
82-
echo "TESTKUBE_NAMESPACE=testkube" >> "$GITHUB_ENV"
83-
echo "VALIDATION_TEST=1" >> "$GITHUB_ENV"
84-
- name: Start cloud-provider-kind
85-
run: |
86-
sudo "$(go env GOPATH)/bin/cloud-provider-kind" > /tmp/cloud-provider-kind.log 2>&1 &
87-
- name: Run provider validation
88-
run: go test -v -run 'TestValidationFunctions|TestInstanceLifecycleValidation|TestFailureInstanceTypesValidation' ./v1/providers/testkube
89-
```
90-
91-
These tests validate inventory, Kubernetes resource creation/listing, SSH access, stop/start/delete, and configured failure types. They do not validate dev-plane software setup.
92-
93-
### Image-Backed SSH Validation
94-
95-
Build the local image:
96-
97-
```bash
98-
docker build -t ghcr.io/brevdev/cloud/testkube-ubuntu-vm:latest ./v1/providers/testkube/images/ubuntu-vm
99-
```
100-
101-
For local minikube validation, load the image into the minikube profile:
102-
103-
```bash
104-
minikube --profile testkube image load ghcr.io/brevdev/cloud/testkube-ubuntu-vm:latest
105-
```
106-
107-
For CI kind validation, load the image into the kind cluster:
108-
109-
```bash
110-
kind load docker-image ghcr.io/brevdev/cloud/testkube-ubuntu-vm:latest --name testkube
111-
```
112-
113-
Then run the focused SSH validation:
114-
115-
```bash
116-
go test -v -run TestImageBackedInstanceValidation ./v1/providers/testkube
117-
```
118-
119-
`TestImageBackedInstanceValidation` creates a `test.ok.cpu` instance with the baked image tag, waits for the pod and load balancer to become ready, uses the provider-returned SSH endpoint, and verifies key-based SSH plus `sudo`, `apt-get`, and systemd basics.
120-
121-
CI can add the same build/load step before running the image-backed test:
122-
123-
```yaml
124-
- name: Build local testkube image
125-
run: docker build -t ghcr.io/brevdev/cloud/testkube-ubuntu-vm:latest ./v1/providers/testkube/images/ubuntu-vm
126-
- name: Load local testkube image into kind
127-
run: kind load docker-image ghcr.io/brevdev/cloud/testkube-ubuntu-vm:latest --name testkube
128-
- name: Run image-backed validation
129-
run: go test -v -run TestImageBackedInstanceValidation ./v1/providers/testkube
130-
```
131-
132-
## Image Contract
133-
134-
`test.ok.cpu` points at `ghcr.io/brevdev/cloud/testkube-ubuntu-vm:latest`. Before that image is published to GHCR, build the local image with that same tag. Once the image is published, local and CI validation can either pull it or keep building and loading the same tag for hermetic tests.
135-
136-
The image at `images/ubuntu-vm` is expected to behave like a minimal Ubuntu VM for dev-plane: SSH access, `sudo`, `apt-get`, and systemd compatibility.
137-
138-
The cloud provider does not lay down dev-plane software; dev-plane owns that. The image only needs to provide a base OS environment that dev-plane setup can use.
139-
140-
Future exposure modes, such as fixed `NodePort` for one-off local debugging, should be separate instance type specs rather than credential fields.
55+
```

v1/providers/testkube/instance.go

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"encoding/hex"
77
"encoding/json"
88
"fmt"
9+
"regexp"
910
"slices"
1011
"strings"
1112
"time"
@@ -365,18 +366,22 @@ func (c *TestKubeClient) instanceFromResources(pod *corev1.Pod, service *corev1.
365366
}
366367

367368
func populateNetwork(service *corev1.Service, instance *cloudv1.Instance) {
369+
// Default the private IP to the cluster IP.
368370
if service.Spec.ClusterIP != "" && service.Spec.ClusterIP != corev1.ClusterIPNone {
369371
instance.PrivateIP = service.Spec.ClusterIP
370372
}
373+
// Default the SSH port to the first open port.
371374
if len(service.Spec.Ports) > 0 {
372375
instance.SSHPort = int(service.Spec.Ports[0].Port)
373376
}
374377

375378
switch service.Spec.Type {
376379
case corev1.ServiceTypeLoadBalancer:
380+
// No ingress means no public IP.
377381
if len(service.Status.LoadBalancer.Ingress) == 0 {
378382
return
379383
}
384+
// Set the public IP to the first ingress IP.
380385
ingress := service.Status.LoadBalancer.Ingress[0]
381386
if ingress.IP != "" {
382387
instance.PublicIP = ingress.IP
@@ -389,11 +394,14 @@ func populateNetwork(service *corev1.Service, instance *cloudv1.Instance) {
389394
}
390395
}
391396
case corev1.ServiceTypeNodePort:
397+
// No ports means no node port.
392398
if len(service.Spec.Ports) == 0 {
393399
return
394400
}
401+
// Set the SSH port to the first node port.
395402
instance.SSHPort = int(service.Spec.Ports[0].NodePort)
396403
case corev1.ServiceTypeClusterIP:
404+
// Keep the cluster IP as the private IP.
397405
}
398406
}
399407

@@ -589,24 +597,15 @@ func tagsFromAnnotations(annotations map[string]string) cloudv1.Tags {
589597
return tags
590598
}
591599

600+
var invalidLabelValueCharPattern = regexp.MustCompile(`[^a-z0-9_.-]`)
601+
602+
const maxLabelValueLength = 63
603+
592604
func sanitizeLabelValue(value string) string {
593-
value = strings.ToLower(value)
594-
var builder strings.Builder
595-
for _, r := range value {
596-
switch {
597-
case r >= 'a' && r <= 'z':
598-
builder.WriteRune(r)
599-
case r >= '0' && r <= '9':
600-
builder.WriteRune(r)
601-
case r == '-' || r == '_' || r == '.':
602-
builder.WriteRune(r)
603-
default:
604-
builder.WriteRune('-')
605-
}
606-
}
607-
sanitized := strings.Trim(builder.String(), "-_.")
608-
if len(sanitized) > 63 {
609-
sanitized = sanitized[:63]
605+
sanitized := invalidLabelValueCharPattern.ReplaceAllString(strings.ToLower(value), "-")
606+
sanitized = strings.Trim(sanitized, "-_.")
607+
if len(sanitized) > maxLabelValueLength {
608+
sanitized = sanitized[:maxLabelValueLength]
610609
sanitized = strings.TrimRight(sanitized, "-_.")
611610
}
612611
if sanitized == "" {

0 commit comments

Comments
 (0)