Skip to content

Commit 37de2e5

Browse files
committed
chore: Merge branch 'main' of https://github.com/openmcp-project/ui-backend
2 parents 3101465 + 1de5847 commit 37de2e5

File tree

6 files changed

+101
-66
lines changed

6 files changed

+101
-66
lines changed

.github/workflows/clean-main-images.yml

Lines changed: 16 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -4,68 +4,31 @@ on:
44
schedule:
55
- cron: "5 1 * * *"
66
workflow_dispatch:
7+
inputs:
8+
dry-run:
9+
description: "Dry run"
10+
required: false
11+
default: true
12+
type: "boolean"
713

814
env:
9-
REGISTRY: ghcr.io
10-
ORG: openmcp-project
1115
IMAGE_NAME: mcp-ui-backend
1216
KEEP_X_IMAGES: 5
1317
TAG_PREFIX: "main-*"
1418

1519
jobs:
1620
clean:
17-
name: "Clean main images"
21+
name: Clean main images
1822
runs-on: ubuntu-latest
1923
permissions:
2024
packages: write
21-
env:
22-
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
2325
steps:
24-
- name: List all ${{ env.TAG_PREFIX }} tags and their version IDs (debug)
25-
run: |
26-
gh api -H "Accept: application/vnd.github+json" \
27-
/orgs/${{ env.ORG }}/packages/container/${{ env.IMAGE_NAME }}/versions \
28-
--paginate | jq -r '.[] | select(.metadata.container.tags[] | startswith("${{ env.TAG_PREFIX }}")) | "\(.id) \(.metadata.container.tags[])"' | grep '^.* ${{ env.TAG_PREFIX }}' | sort -k2 -r
29-
30-
- name: Delete old ${{ env.TAG_PREFIX }}* tags using GitHub API, keep ${{ env.KEEP_X_IMAGES }}
31-
run: |
32-
set -e
33-
set -o pipefail
34-
35-
# Get all ${{ env.TAG_PREFIX }}* tags and their version IDs, sorted by tag (descending)
36-
VERSIONS=$(gh api -H "Accept: application/vnd.github+json" \
37-
/orgs/${{ env.ORG }}/packages/container/${{ env.IMAGE_NAME }}/versions \
38-
--paginate | jq -r '.[] | select(.metadata.container.tags[] | startswith("${{ env.TAG_PREFIX }}")) | "\(.id) \(.metadata.container.tags[])"' | grep '^.* ${{ env.TAG_PREFIX }}' | sort -k2 -r)
39-
40-
# Get the lines to delete (skip the first ${{ env.KEEP_X_IMAGES }} versions)
41-
TO_DELETE=$(echo "$VERSIONS" | sed "1,${{ env.KEEP_X_IMAGES }}d")
42-
43-
echo "Deleting the following tags:"
44-
echo "$TO_DELETE" | awk '{print $2}'
45-
46-
if [ -z "$TO_DELETE" ]; then
47-
echo "No tags to delete."
48-
exit 0
49-
fi
50-
51-
FAILED_DELETIONS=""
52-
while read -r line; do
53-
id=$(echo "$line" | awk '{print $1}')
54-
tag=$(echo "$line" | awk '{print $2}')
55-
echo "Deleting tag $tag (version ID $id)"
56-
if ! gh api -X DELETE -H "Accept: application/vnd.github+json" \
57-
/orgs/${{ env.ORG }}/packages/container/${{ env.IMAGE_NAME }}/versions/$id; then
58-
echo "Failed to delete version $id ($tag)"
59-
FAILED_DELETIONS="${FAILED_DELETIONS}\n$id ($tag)"
60-
fi
61-
done <<< "$TO_DELETE"
62-
63-
if [ -n "$FAILED_DELETIONS" ]; then
64-
echo -e "The following deletions failed:\n$FAILED_DELETIONS"
65-
exit 1
66-
fi
67-
- name: List remaining ${{ env.TAG_PREFIX }}* tags and their version IDs (debug)
68-
run: |
69-
gh api -H "Accept: application/vnd.github+json" \
70-
/orgs/${{ env.ORG }}/packages/container/${{ env.IMAGE_NAME }}/versions \
71-
--paginate | jq -r '.[] | select(.metadata.container.tags[] | startswith("${{ env.TAG_PREFIX }}")) | "\(.id) \(.metadata.container.tags[])"' | grep '^.* ${{ env.TAG_PREFIX }}' | sort -k2 -r
26+
- uses: dataaxiom/ghcr-cleanup-action@cd0cdb900b5dbf3a6f2cc869f0dbb0b8211f50c4 #v1
27+
with:
28+
dry-run: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.dry-run == 'true' }}
29+
packages: ${{ env.IMAGE_NAME }}
30+
delete-tags: ${{ env.TAG_PREFIX }}
31+
delete-untagged: true
32+
keep-n-tagged: ${{ env.KEEP_X_IMAGES }}
33+
delete-ghost-images: true
34+
delete-partial-images: true

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ tmp
88

99
coverage.html
1010
coverage.out
11+
.vscode

internal/server/handlerCategory.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,16 @@ func _categoryHandler(s *shared, req *http.Request, res *response) (*response, *
4646

4747
var config k8s.KubeConfig
4848
if data.ProjectName != "" && data.WorkspaceName != "" && data.McpName != "" {
49-
config, err = openmcp.GetControlPlaneKubeconfig(s.crateKube, data.ProjectName, data.WorkspaceName, data.McpName, data.Authorization, crateKubeconfig)
49+
config, err = openmcp.GetControlPlaneKubeconfig(s.crateKube, data.ProjectName, data.WorkspaceName, data.McpName, data.CrateAuthorizationToken, crateKubeconfig)
5050
if err != nil {
5151
slog.Error("failed to get control plane api config", "err", err)
5252
return nil, NewInternalServerError("failed to get control plane api config")
5353
}
54-
if data.Authorization != "" {
55-
config.SetUserToken(data.Authorization)
54+
if data.McpAuthorizationToken == "" {
55+
slog.Error("MCP authorization token not provided")
56+
return nil, NewBadRequestError("MCP authorization token not provided")
5657
}
58+
config.SetUserToken(data.McpAuthorizationToken)
5759
} else {
5860
slog.Error("either use %s: true or provide %s, %s and %s headers", useCrateClusterHeader, projectNameHeader, workspaceNameHeader, mcpName)
5961
return nil, NewBadRequestError(

internal/server/handlerMain.go

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ type ExtractedRequestData struct {
5454
McpName string
5555
ContextName string
5656
UseCrateCluster bool
57-
Authorization string
57+
CrateAuthorizationToken string
58+
McpAuthorizationToken string
5859
Headers map[string][]string
5960
JQ string
6061
Category string
@@ -87,16 +88,18 @@ func mainHandler(s *shared, req *http.Request, res *response) (*response, *HttpE
8788
var config k8s.KubeConfig
8889
if data.UseCrateCluster {
8990
config = crateKubeconfig
90-
config.SetUserToken(data.Authorization)
91+
config.SetUserToken(data.CrateAuthorizationToken)
9192
} else if data.ProjectName != "" && data.WorkspaceName != "" && data.McpName != "" {
92-
config, err = openmcp.GetControlPlaneKubeconfig(s.crateKube, data.ProjectName, data.WorkspaceName, data.McpName, data.Authorization, crateKubeconfig)
93+
config, err = openmcp.GetControlPlaneKubeconfig(s.crateKube, data.ProjectName, data.WorkspaceName, data.McpName, data.CrateAuthorizationToken, crateKubeconfig)
9394
if err != nil {
9495
slog.Error("failed to get control plane api config", "err", err)
9596
return nil, NewInternalServerError("failed to get control plane api config")
9697
}
97-
if data.Authorization != "" {
98-
config.SetUserToken(data.Authorization)
98+
if data.McpAuthorizationToken == "" {
99+
slog.Error("MCP authorization token not provided")
100+
return nil, NewBadRequestError("MCP authorization token not provided")
99101
}
102+
config.SetUserToken(data.McpAuthorizationToken)
100103
} else {
101104
slog.Error("either use %s: true or provide %s, %s and %s headers", useCrateClusterHeader, projectNameHeader, workspaceNameHeader, mcpName)
102105
return nil, NewBadRequestError(
@@ -138,6 +141,15 @@ func mainHandler(s *shared, req *http.Request, res *response) (*response, *HttpE
138141
}
139142

140143
func extractRequestData(r *http.Request) (ExtractedRequestData, error) {
144+
if r.Header.Get(authorizationHeader) == "" {
145+
return ExtractedRequestData{}, fmt.Errorf("%s header is required", authorizationHeader)
146+
}
147+
148+
crateToken, mcpToken, err := parseAuthorizationHeaderWithDoubleTokens(r.Header.Get(authorizationHeader))
149+
if err != nil {
150+
return ExtractedRequestData{}, fmt.Errorf("invalid %s header: %w", authorizationHeader, err)
151+
}
152+
141153
rd := ExtractedRequestData{
142154
Path: r.URL.Path,
143155
Query: r.URL.Query(),
@@ -150,7 +162,8 @@ func extractRequestData(r *http.Request) (ExtractedRequestData, error) {
150162
WorkspaceName: r.Header.Get(workspaceNameHeader),
151163
ContextName: r.Header.Get(contextHeader),
152164
McpName: r.Header.Get(mcpName),
153-
Authorization: r.Header.Get(authorizationHeader),
165+
CrateAuthorizationToken: crateToken,
166+
McpAuthorizationToken: mcpToken,
154167
JQ: r.Header.Get(jqHeader),
155168
Category: r.Header.Get(categoryHeader),
156169
}
@@ -166,10 +179,6 @@ func extractRequestData(r *http.Request) (ExtractedRequestData, error) {
166179
rd.UseCrateCluster = useCrateCluster
167180
}
168181

169-
if rd.Authorization == "" {
170-
return ExtractedRequestData{}, fmt.Errorf("%s header is required", authorizationHeader)
171-
}
172-
173182
return rd, nil
174183
}
175184

internal/server/utils.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package server
33
import (
44
"bytes"
55
"encoding/json"
6+
"fmt"
67
"io"
78
"net/http"
89
"strings"
@@ -103,3 +104,21 @@ func ParseJQ(inputJson []byte, inputJQ string) (string, error) {
103104

104105
return strings.Join(result[:], "\n"), nil
105106
}
107+
108+
// parseAuthorizationHeaderWithDoubleTokens parses an authorization header that may contain two tokens separated by a comma.
109+
// It returns the first token and the second token (if present). If the second token is absent, it returns an empty string for it.
110+
// If the header is empty or contains more than two tokens, it returns an error.
111+
func parseAuthorizationHeaderWithDoubleTokens(authHeader string) (string, string, error) {
112+
if authHeader == "" {
113+
return "", "", fmt.Errorf("authorization header is empty")
114+
}
115+
116+
tokens := strings.Split(authHeader, ",")
117+
if len(tokens) > 2 {
118+
return "", "", fmt.Errorf("authorization header must contain two or less tokens separated by a space")
119+
}
120+
if len(tokens) == 1 {
121+
return tokens[0], "", nil
122+
}
123+
return tokens[0], tokens[1], nil
124+
}

internal/server/utils_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package server
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestParseAuthorizationHeaderWithDoubleTokens(t *testing.T) {
8+
tests := []struct {
9+
authHeader string
10+
token1 string
11+
token2 string
12+
expectErr bool
13+
}{
14+
{"token1,token2", "token1", "token2", false},
15+
{"token1", "token1", "", false},
16+
{"", "", "", true},
17+
{"token1,token2,token3", "", "", true},
18+
}
19+
20+
for _, test := range tests {
21+
t.Run(test.authHeader, func(t *testing.T) {
22+
token1, token2, err := parseAuthorizationHeaderWithDoubleTokens(test.authHeader)
23+
24+
if test.expectErr {
25+
if err == nil {
26+
t.Errorf("expected an error but got none")
27+
}
28+
} else {
29+
if err != nil {
30+
t.Errorf("expected no error but got: %v", err)
31+
}
32+
if token1 != test.token1 {
33+
t.Errorf("expected token1 to be %q but got %q", test.token1, token1)
34+
}
35+
if token2 != test.token2 {
36+
t.Errorf("expected token2 to be %q but got %q", test.token2, token2)
37+
}
38+
}
39+
})
40+
}
41+
}

0 commit comments

Comments
 (0)