Skip to content

Commit 0e7969b

Browse files
committed
ensures builds succeed after repo restructure
1 parent aff4c62 commit 0e7969b

30 files changed

Lines changed: 1726 additions & 0 deletions

demos/README.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Runtime Conditions Demos
2+
3+
This tree contains runnable examples and downstream adapter assets.
4+
5+
## Layout
6+
7+
- `apps/request-logger-http/` - Go workload that imports first-party declaration packages and demonstrates explicit profile declarations.
8+
- `apps/todos-api/` - simple provider API used by the request logger demo.
9+
- `catalog/apis/` - OpenAPI and catalog files used by the adapter demo.
10+
- `kratix/` - Kratix Promise and adapter assets for downstream fulfillment demos.
11+
12+
## Generate the Request Logger Profile
13+
14+
From the repository root:
15+
16+
```sh
17+
cd go/profiler
18+
go run . \
19+
-dir ../../demos/apps/request-logger-http \
20+
-name request-logger-http \
21+
-workload-version dev
22+
```
23+
24+
The request logger is its own Go module:
25+
26+
```sh
27+
cd demos/apps/request-logger-http
28+
go test ./...
29+
```
30+
31+
## Published Demo Images
32+
33+
The workflow at `.github/workflows/publish-ghcr-images.yml` builds and pushes images for:
34+
35+
- `redis-pipeline`
36+
- `application-release-pipeline`
37+
- `todos-api`
38+
- `request-logger`
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
FROM golang:1.25-alpine AS build
2+
3+
WORKDIR /src
4+
COPY extensions ./extensions
5+
COPY demos/apps/request-logger-http/go.mod ./demos/apps/request-logger-http/go.mod
6+
COPY demos/apps/request-logger-http/*.go ./demos/apps/request-logger-http/
7+
8+
WORKDIR /src/demos/apps/request-logger-http
9+
RUN go mod download
10+
RUN go build -o /out/request-logger .
11+
12+
FROM gcr.io/distroless/static-debian12
13+
14+
COPY --from=build /out/request-logger /request-logger
15+
16+
ENTRYPOINT ["/request-logger"]
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package main
2+
3+
import (
4+
common "github.com/colinjlacy/runtime-conditions-profiles/extensions/common-integrations/go"
5+
env "github.com/colinjlacy/runtime-conditions-profiles/extensions/env-configuration/go"
6+
)
7+
8+
func declaration() {
9+
if 1 != 1 {
10+
common.API("todos-api",
11+
common.Spec("openapi", "catalog://api/default/todos-api", "1.0.0"),
12+
common.GET("/todos/{id}", common.Response[Todo]()),
13+
env.Env("baseUrl", "TODOS_API_URL"),
14+
)
15+
common.Cache("request-cache",
16+
common.KeyValue(common.Redis),
17+
env.EnvAlternative(env.Env("url", "REDIS_URL")),
18+
env.EnvAlternative(
19+
env.Env("hostname", "REDIS_HOST"),
20+
env.Env("port", "REDIS_PORT"),
21+
),
22+
)
23+
}
24+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
module github.com/colinjlacy/runtime-conditions-profiles/demos/apps/request-logger-http
2+
3+
go 1.25.0
4+
5+
require (
6+
github.com/colinjlacy/runtime-conditions-profiles/extensions/common-integrations/go v0.0.0
7+
github.com/colinjlacy/runtime-conditions-profiles/extensions/env-configuration/go v0.0.0
8+
)
9+
10+
replace github.com/colinjlacy/runtime-conditions-profiles/extensions/common-integrations/go => ../../../extensions/common-integrations/go
11+
12+
replace github.com/colinjlacy/runtime-conditions-profiles/extensions/env-configuration/go => ../../../extensions/env-configuration/go
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
package main
2+
3+
import (
4+
"bufio"
5+
"context"
6+
"encoding/json"
7+
"errors"
8+
"fmt"
9+
"log"
10+
"net"
11+
"net/http"
12+
"net/url"
13+
"os"
14+
"strings"
15+
"time"
16+
)
17+
18+
type Todo struct {
19+
ID int `json:"id"`
20+
Title string `json:"title"`
21+
Completed bool `json:"completed"`
22+
}
23+
24+
func init() {
25+
declaration()
26+
}
27+
28+
func main() {
29+
mux := http.NewServeMux()
30+
mux.HandleFunc("/ready", readinessHandler)
31+
mux.HandleFunc("/demo", demoHandler)
32+
33+
addr := ":" + envOrDefault("PORT", "8080")
34+
log.Printf("request-logger demo listening on %s", addr)
35+
if err := http.ListenAndServe(addr, mux); err != nil {
36+
log.Fatal(err)
37+
}
38+
}
39+
40+
func readinessHandler(w http.ResponseWriter, r *http.Request) {
41+
ctx, cancel := context.WithTimeout(r.Context(), 3*time.Second)
42+
defer cancel()
43+
44+
if err := checkTodosAPI(ctx); err != nil {
45+
http.Error(w, err.Error(), http.StatusServiceUnavailable)
46+
return
47+
}
48+
if err := checkRedis(ctx); err != nil {
49+
http.Error(w, err.Error(), http.StatusServiceUnavailable)
50+
return
51+
}
52+
w.WriteHeader(http.StatusNoContent)
53+
}
54+
55+
func demoHandler(w http.ResponseWriter, r *http.Request) {
56+
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
57+
defer cancel()
58+
59+
result := map[string]string{
60+
"todosApi": statusString(checkTodosAPI(ctx)),
61+
"cache": statusString(checkRedis(ctx)),
62+
}
63+
64+
status := http.StatusOK
65+
if result["todosApi"] != "ok" || result["cache"] != "ok" {
66+
status = http.StatusInternalServerError
67+
}
68+
69+
w.Header().Set("Content-Type", "application/json")
70+
w.WriteHeader(status)
71+
if status == http.StatusInternalServerError {
72+
w.Write([]byte(fmt.Sprintf("One or more dependencies are not healthy: todosApi=%s, cache=%s", result["todosApi"], result["cache"])))
73+
}
74+
if err := json.NewEncoder(w).Encode(result); err != nil {
75+
log.Printf("encode response: %v", err)
76+
}
77+
}
78+
79+
func checkTodosAPI(ctx context.Context) error {
80+
baseURL := strings.TrimRight(os.Getenv("TODOS_API_URL"), "/")
81+
if baseURL == "" {
82+
return errors.New("TODOS_API_URL is not set")
83+
}
84+
85+
request, err := http.NewRequestWithContext(ctx, http.MethodGet, baseURL+"/todos/1", nil)
86+
if err != nil {
87+
return err
88+
}
89+
response, err := http.DefaultClient.Do(request)
90+
if err != nil {
91+
return fmt.Errorf("todos-api request failed: %w", err)
92+
}
93+
defer response.Body.Close()
94+
if response.StatusCode != http.StatusOK {
95+
return fmt.Errorf("todos-api returned %s", response.Status)
96+
}
97+
98+
var todo Todo
99+
if err := json.NewDecoder(response.Body).Decode(&todo); err != nil {
100+
return fmt.Errorf("todos-api response decode failed: %w", err)
101+
}
102+
if todo.ID == 0 || todo.Title == "" {
103+
return errors.New("todos-api response was incomplete")
104+
}
105+
return nil
106+
}
107+
108+
func checkRedis(ctx context.Context) error {
109+
addr, err := redisAddress()
110+
if err != nil {
111+
return err
112+
}
113+
114+
var dialer net.Dialer
115+
conn, err := dialer.DialContext(ctx, "tcp", addr)
116+
if err != nil {
117+
return fmt.Errorf("redis dial failed: %w", err)
118+
}
119+
defer conn.Close()
120+
121+
deadline, ok := ctx.Deadline()
122+
if ok {
123+
_ = conn.SetDeadline(deadline)
124+
}
125+
if _, err := conn.Write([]byte("*1\r\n$4\r\nPING\r\n")); err != nil {
126+
return fmt.Errorf("redis ping write failed: %w", err)
127+
}
128+
line, err := bufio.NewReader(conn).ReadString('\n')
129+
if err != nil {
130+
return fmt.Errorf("redis ping read failed: %w", err)
131+
}
132+
if !strings.HasPrefix(line, "+PONG") {
133+
return fmt.Errorf("redis ping returned %q", strings.TrimSpace(line))
134+
}
135+
return nil
136+
}
137+
138+
func redisAddress() (string, error) {
139+
if rawURL := os.Getenv("REDIS_URL"); rawURL != "" {
140+
parsed, err := url.Parse(rawURL)
141+
if err != nil {
142+
return "", fmt.Errorf("invalid REDIS_URL: %w", err)
143+
}
144+
if parsed.Host != "" {
145+
return parsed.Host, nil
146+
}
147+
}
148+
149+
host := os.Getenv("REDIS_HOST")
150+
port := envOrDefault("REDIS_PORT", "6379")
151+
if host == "" {
152+
return "", errors.New("REDIS_URL or REDIS_HOST must be set")
153+
}
154+
return net.JoinHostPort(host, port), nil
155+
}
156+
157+
func statusString(err error) string {
158+
if err != nil {
159+
log.Print(err)
160+
return "error"
161+
}
162+
return "ok"
163+
}
164+
165+
func envOrDefault(key, fallback string) string {
166+
value := os.Getenv(key)
167+
if value == "" {
168+
return fallback
169+
}
170+
return value
171+
}

demos/apps/todos-api/Dockerfile

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
FROM golang:1.25-alpine AS build
2+
3+
WORKDIR /src
4+
COPY go.mod ./
5+
COPY main.go ./
6+
RUN go build -o /out/todos-api .
7+
8+
FROM gcr.io/distroless/static-debian12
9+
10+
COPY --from=build /out/todos-api /todos-api
11+
12+
ENTRYPOINT ["/todos-api"]
13+

demos/apps/todos-api/go.mod

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module runtimeconditions-demo/todos-api
2+
3+
go 1.25.0
4+

demos/apps/todos-api/k8s.yaml.tmpl

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
apiVersion: apps/v1
2+
kind: Deployment
3+
metadata:
4+
name: __TODOS_API_NAME__
5+
namespace: __DEMO_NAMESPACE__
6+
labels:
7+
app.kubernetes.io/name: __TODOS_API_NAME__
8+
app.kubernetes.io/component: provider-api
9+
spec:
10+
replicas: 1
11+
selector:
12+
matchLabels:
13+
app.kubernetes.io/name: __TODOS_API_NAME__
14+
template:
15+
metadata:
16+
labels:
17+
app.kubernetes.io/name: __TODOS_API_NAME__
18+
app.kubernetes.io/component: provider-api
19+
spec:
20+
containers:
21+
- name: todos-api
22+
image: __TODOS_API_IMAGE__
23+
imagePullPolicy: IfNotPresent
24+
ports:
25+
- name: http
26+
containerPort: __TODOS_API_PORT__
27+
env:
28+
- name: PORT
29+
value: "__TODOS_API_PORT__"
30+
readinessProbe:
31+
httpGet:
32+
path: /ready
33+
port: http
34+
initialDelaySeconds: 2
35+
periodSeconds: 5
36+
---
37+
apiVersion: v1
38+
kind: Service
39+
metadata:
40+
name: __TODOS_API_NAME__
41+
namespace: __DEMO_NAMESPACE__
42+
labels:
43+
app.kubernetes.io/name: __TODOS_API_NAME__
44+
spec:
45+
selector:
46+
app.kubernetes.io/name: __TODOS_API_NAME__
47+
ports:
48+
- name: http
49+
port: __TODOS_API_PORT__
50+
targetPort: http
51+

demos/apps/todos-api/main.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"log"
6+
"net/http"
7+
"os"
8+
"strconv"
9+
"strings"
10+
)
11+
12+
type Todo struct {
13+
ID int `json:"id"`
14+
Title string `json:"title"`
15+
Completed bool `json:"completed"`
16+
}
17+
18+
func main() {
19+
mux := http.NewServeMux()
20+
mux.HandleFunc("/ready", func(w http.ResponseWriter, _ *http.Request) {
21+
w.WriteHeader(http.StatusNoContent)
22+
})
23+
mux.HandleFunc("/todos/", getTodo)
24+
25+
addr := ":" + envOrDefault("PORT", "8080")
26+
log.Printf("todos-api listening on %s", addr)
27+
if err := http.ListenAndServe(addr, mux); err != nil {
28+
log.Fatal(err)
29+
}
30+
}
31+
32+
func getTodo(w http.ResponseWriter, r *http.Request) {
33+
if r.Method != http.MethodGet {
34+
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
35+
return
36+
}
37+
38+
idPart := strings.TrimPrefix(r.URL.Path, "/todos/")
39+
id, err := strconv.Atoi(idPart)
40+
if err != nil {
41+
http.Error(w, "invalid todo id", http.StatusBadRequest)
42+
return
43+
}
44+
45+
w.Header().Set("Content-Type", "application/json")
46+
if err := json.NewEncoder(w).Encode(Todo{
47+
ID: id,
48+
Title: "runtime conditions demo",
49+
Completed: false,
50+
}); err != nil {
51+
log.Printf("encode response: %v", err)
52+
}
53+
}
54+
55+
func envOrDefault(key, fallback string) string {
56+
value := os.Getenv(key)
57+
if value == "" {
58+
return fallback
59+
}
60+
return value
61+
}
62+

0 commit comments

Comments
 (0)