Skip to content

Commit 798138c

Browse files
authored
Merge pull request #50 from cyclops-k8s/ec-endpointslices
Convert to endpoint slices
2 parents ac5639a + 8e184ee commit 798138c

45 files changed

Lines changed: 5240 additions & 1013 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.config/dotnet-tools.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"isRoot": true,
44
"tools": {
55
"kubeops.cli": {
6-
"version": "10.0.3",
6+
"version": "10.3.2",
77
"commands": [
88
"kubeops"
99
],

.devcontainer/devcontainer.json

Lines changed: 38 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,39 @@
11
{
2-
"name": "Vecc.K8s.MultiCluster",
3-
"image": "mcr.microsoft.com/devcontainers/dotnet:10.0",
4-
5-
"features": {
6-
"ghcr.io/devcontainers/features/docker-in-docker:2": {
7-
"version": "latest",
8-
"dockerDashComposeVersion": "v2"
9-
},
10-
"ghcr.io/devcontainers/features/kubectl-helm-minikube:1": {
11-
"version": "latest",
12-
"helm": "latest",
13-
"minikube": "none"
14-
}
15-
},
16-
17-
"customizations": {
18-
"vscode": {
19-
"extensions": [
20-
"editorconfig.editorconfig",
21-
"ms-dotnettools.csharp",
22-
"ms-dotnettools.csdevkit",
23-
"ms-kubernetes-tools.vscode-kubernetes-tools",
24-
"ms-azuretools.vscode-docker",
25-
"redhat.vscode-yaml"
26-
],
27-
"settings": {
28-
"dotnet.server.useOmnisharp": false
29-
}
30-
}
31-
},
32-
33-
"forwardPorts": [5000, 5001],
34-
35-
"postCreateCommand": "sudo apt-get update && sudo apt-get install -y dnsutils kubectx",
36-
37-
"remoteUser": "vscode",
38-
39-
"mounts": [
40-
"source=${localEnv:HOME}${localEnv:USERPROFILE}/.ssh,target=/home/vscode/.ssh,readonly,type=bind"
41-
]
42-
}
2+
"name": "Vecc.K8s.MultiCluster",
3+
"image": "mcr.microsoft.com/devcontainers/dotnet:10.0",
4+
"features": {
5+
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {
6+
"version": "latest",
7+
"dockerDashComposeVersion": "v2"
8+
},
9+
"ghcr.io/devcontainers/features/kubectl-helm-minikube:1": {
10+
"version": "latest",
11+
"helm": "latest",
12+
"minikube": "none"
13+
}
14+
},
15+
"customizations": {
16+
"vscode": {
17+
"extensions": [
18+
"editorconfig.editorconfig",
19+
"ms-dotnettools.csharp",
20+
"ms-dotnettools.csdevkit",
21+
"ms-kubernetes-tools.vscode-kubernetes-tools",
22+
"ms-azuretools.vscode-docker",
23+
"redhat.vscode-yaml"
24+
],
25+
"settings": {
26+
"dotnet.server.useOmnisharp": false
27+
}
28+
}
29+
},
30+
"forwardPorts": [
31+
5000,
32+
5001
33+
],
34+
"postCreateCommand": "sudo apt-get update && sudo apt-get install -y dnsutils kubectx",
35+
"remoteUser": "vscode",
36+
"mounts": [
37+
"source=${localEnv:HOME}${localEnv:USERPROFILE}/.ssh,target=/home/vscode/.ssh,readonly,type=bind"
38+
]
39+
}

.gitattributes

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
11
*.sh text eol=lf
22
test/**/* text eol=lf
3-
*.cs text eol=crlf

Dockerfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,13 @@ EXPOSE 80
77
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
88
WORKDIR /src
99
COPY ["src/Vecc.K8s.MultiCluster.Api/Vecc.K8s.MultiCluster.Api.csproj", "src/Vecc.K8s.MultiCluster.Api/"]
10+
COPY ["src/Vecc.K8s.MultiCluster.Api.Tests/Vecc.K8s.MultiCluster.Api.Tests.csproj", "src/Vecc.K8s.MultiCluster.Api.Tests/"]
1011
RUN dotnet restore "src/Vecc.K8s.MultiCluster.Api/Vecc.K8s.MultiCluster.Api.csproj"
12+
RUN dotnet restore "src/Vecc.K8s.MultiCluster.Api.Tests/Vecc.K8s.MultiCluster.Api.Tests.csproj"
1113
COPY . .
1214
RUN dotnet build "src/Vecc.K8s.MultiCluster.Api/Vecc.K8s.MultiCluster.Api.csproj" -c Release
15+
RUN dotnet build "src/Vecc.K8s.MultiCluster.Api.Tests/Vecc.K8s.MultiCluster.Api.Tests.csproj" -c Release
16+
RUN dotnet test "src/Vecc.K8s.MultiCluster.Api.Tests/Vecc.K8s.MultiCluster.Api.Tests.csproj" -c Release --no-build
1317

1418
FROM build AS publish
1519
RUN dotnet publish "src/Vecc.K8s.MultiCluster.Api/Vecc.K8s.MultiCluster.Api.csproj" -c Release -o /app/publish /p:UseAppHost=false

Vecc.K8s.MultiCluster.sln

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,70 @@
11

22
Microsoft Visual Studio Solution File, Format Version 12.00
33
# Visual Studio Version 18
4-
VisualStudioVersion = 18.1.11304.174 d18.0
4+
VisualStudioVersion = 18.1.11304.174
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{D2E1A479-9A98-4F6D-934E-645ADA1B62D0}"
77
EndProject
88
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Vecc.K8s.MultiCluster.Api", "src\Vecc.K8s.MultiCluster.Api\Vecc.K8s.MultiCluster.Api.csproj", "{42A2A630-C590-497E-B678-8AC01597644D}"
99
EndProject
1010
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Vecc.IngressOperator", "test\ingress-operator\Vecc.IngressOperator.csproj", "{C5D31554-4347-F128-103B-30EE4B035182}"
1111
EndProject
12+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Vecc.K8s.MultiCluster.Api.Tests", "src\Vecc.K8s.MultiCluster.Api.Tests\Vecc.K8s.MultiCluster.Api.Tests.csproj", "{286F8C83-014A-4BD0-8518-51AEB462289D}"
13+
EndProject
1214
Global
1315
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1416
Debug|Any CPU = Debug|Any CPU
17+
Debug|x64 = Debug|x64
18+
Debug|x86 = Debug|x86
1519
Release|Any CPU = Release|Any CPU
20+
Release|x64 = Release|x64
21+
Release|x86 = Release|x86
1622
EndGlobalSection
1723
GlobalSection(ProjectConfigurationPlatforms) = postSolution
1824
{42A2A630-C590-497E-B678-8AC01597644D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
1925
{42A2A630-C590-497E-B678-8AC01597644D}.Debug|Any CPU.Build.0 = Debug|Any CPU
26+
{42A2A630-C590-497E-B678-8AC01597644D}.Debug|x64.ActiveCfg = Debug|Any CPU
27+
{42A2A630-C590-497E-B678-8AC01597644D}.Debug|x64.Build.0 = Debug|Any CPU
28+
{42A2A630-C590-497E-B678-8AC01597644D}.Debug|x86.ActiveCfg = Debug|Any CPU
29+
{42A2A630-C590-497E-B678-8AC01597644D}.Debug|x86.Build.0 = Debug|Any CPU
2030
{42A2A630-C590-497E-B678-8AC01597644D}.Release|Any CPU.ActiveCfg = Release|Any CPU
2131
{42A2A630-C590-497E-B678-8AC01597644D}.Release|Any CPU.Build.0 = Release|Any CPU
32+
{42A2A630-C590-497E-B678-8AC01597644D}.Release|x64.ActiveCfg = Release|Any CPU
33+
{42A2A630-C590-497E-B678-8AC01597644D}.Release|x64.Build.0 = Release|Any CPU
34+
{42A2A630-C590-497E-B678-8AC01597644D}.Release|x86.ActiveCfg = Release|Any CPU
35+
{42A2A630-C590-497E-B678-8AC01597644D}.Release|x86.Build.0 = Release|Any CPU
2236
{C5D31554-4347-F128-103B-30EE4B035182}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
2337
{C5D31554-4347-F128-103B-30EE4B035182}.Debug|Any CPU.Build.0 = Debug|Any CPU
38+
{C5D31554-4347-F128-103B-30EE4B035182}.Debug|x64.ActiveCfg = Debug|Any CPU
39+
{C5D31554-4347-F128-103B-30EE4B035182}.Debug|x64.Build.0 = Debug|Any CPU
40+
{C5D31554-4347-F128-103B-30EE4B035182}.Debug|x86.ActiveCfg = Debug|Any CPU
41+
{C5D31554-4347-F128-103B-30EE4B035182}.Debug|x86.Build.0 = Debug|Any CPU
2442
{C5D31554-4347-F128-103B-30EE4B035182}.Release|Any CPU.ActiveCfg = Release|Any CPU
2543
{C5D31554-4347-F128-103B-30EE4B035182}.Release|Any CPU.Build.0 = Release|Any CPU
44+
{C5D31554-4347-F128-103B-30EE4B035182}.Release|x64.ActiveCfg = Release|Any CPU
45+
{C5D31554-4347-F128-103B-30EE4B035182}.Release|x64.Build.0 = Release|Any CPU
46+
{C5D31554-4347-F128-103B-30EE4B035182}.Release|x86.ActiveCfg = Release|Any CPU
47+
{C5D31554-4347-F128-103B-30EE4B035182}.Release|x86.Build.0 = Release|Any CPU
48+
{286F8C83-014A-4BD0-8518-51AEB462289D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
49+
{286F8C83-014A-4BD0-8518-51AEB462289D}.Debug|Any CPU.Build.0 = Debug|Any CPU
50+
{286F8C83-014A-4BD0-8518-51AEB462289D}.Debug|x64.ActiveCfg = Debug|Any CPU
51+
{286F8C83-014A-4BD0-8518-51AEB462289D}.Debug|x64.Build.0 = Debug|Any CPU
52+
{286F8C83-014A-4BD0-8518-51AEB462289D}.Debug|x86.ActiveCfg = Debug|Any CPU
53+
{286F8C83-014A-4BD0-8518-51AEB462289D}.Debug|x86.Build.0 = Debug|Any CPU
54+
{286F8C83-014A-4BD0-8518-51AEB462289D}.Release|Any CPU.ActiveCfg = Release|Any CPU
55+
{286F8C83-014A-4BD0-8518-51AEB462289D}.Release|Any CPU.Build.0 = Release|Any CPU
56+
{286F8C83-014A-4BD0-8518-51AEB462289D}.Release|x64.ActiveCfg = Release|Any CPU
57+
{286F8C83-014A-4BD0-8518-51AEB462289D}.Release|x64.Build.0 = Release|Any CPU
58+
{286F8C83-014A-4BD0-8518-51AEB462289D}.Release|x86.ActiveCfg = Release|Any CPU
59+
{286F8C83-014A-4BD0-8518-51AEB462289D}.Release|x86.Build.0 = Release|Any CPU
2660
EndGlobalSection
2761
GlobalSection(SolutionProperties) = preSolution
2862
HideSolutionNode = FALSE
2963
EndGlobalSection
3064
GlobalSection(NestedProjects) = preSolution
3165
{42A2A630-C590-497E-B678-8AC01597644D} = {D2E1A479-9A98-4F6D-934E-645ADA1B62D0}
3266
{C5D31554-4347-F128-103B-30EE4B035182} = {D2E1A479-9A98-4F6D-934E-645ADA1B62D0}
67+
{286F8C83-014A-4BD0-8518-51AEB462289D} = {D2E1A479-9A98-4F6D-934E-645ADA1B62D0}
3368
EndGlobalSection
3469
GlobalSection(ExtensibilityGlobals) = postSolution
3570
SolutionGuid = {7F434610-6131-4036-A349-82A0EC3CA201}

charts/multicluster-ingress/templates/operator-role.yaml

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,29 @@ rules:
2929
- '*'
3030
- apiGroups:
3131
- networking.k8s.io
32-
- ""
3332
resources:
3433
- ingresses
34+
verbs:
35+
- get
36+
- list
37+
- watch
38+
- apiGroups:
39+
- ""
40+
resources:
3541
- services
36-
- endpoints
3742
- namespaces
3843
verbs:
3944
- get
4045
- list
4146
- watch
47+
- apiGroups:
48+
- discovery.k8s.io
49+
resources:
50+
- endpointslices
51+
verbs:
52+
- get
53+
- list
54+
- watch
4255
- apiGroups:
4356
- networking.k8s.io
4457
resources:
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
using Microsoft.AspNetCore.Http;
2+
using Microsoft.AspNetCore.Mvc;
3+
using Microsoft.Extensions.Options;
4+
using Moq;
5+
using Vecc.K8s.MultiCluster.Api.Controllers;
6+
using Vecc.K8s.MultiCluster.Api.Models.Api;
7+
using Vecc.K8s.MultiCluster.Api.Services;
8+
using Vecc.K8s.MultiCluster.Api.Services.Authentication;
9+
10+
namespace Vecc.K8s.MultiCluster.Api.Tests.Controllers
11+
{
12+
public class AuthenticationControllerTests
13+
{
14+
private readonly Mock<IOptions<ApiAuthenticationHandlerOptions>> _authOptionsMock;
15+
private readonly Mock<IOptions<MultiClusterOptions>> _optionsMock;
16+
private readonly ApiAuthenticationHasher _hasher;
17+
private readonly AuthenticationController _controller;
18+
19+
public AuthenticationControllerTests()
20+
{
21+
_authOptionsMock = new Mock<IOptions<ApiAuthenticationHandlerOptions>>();
22+
_optionsMock = new Mock<IOptions<MultiClusterOptions>>();
23+
24+
var authOptions = new ApiAuthenticationHandlerOptions
25+
{
26+
ApiKeys = new[]
27+
{
28+
new ApiKey { ClusterIdentifier = "cluster1", Key = "key1" }
29+
}
30+
};
31+
_authOptionsMock.Setup(x => x.Value).Returns(authOptions);
32+
33+
var multiClusterOptions = new MultiClusterOptions
34+
{
35+
ClusterIdentifier = "local-cluster",
36+
ClusterSalt = new byte[] { 1, 2, 3, 4 }
37+
};
38+
_optionsMock.Setup(x => x.Value).Returns(multiClusterOptions);
39+
40+
_hasher = new ApiAuthenticationHasher(_optionsMock.Object);
41+
42+
_controller = new AuthenticationController(_authOptionsMock.Object, _hasher, _optionsMock.Object);
43+
_controller.ControllerContext = new ControllerContext
44+
{
45+
HttpContext = new DefaultHttpContext()
46+
};
47+
_controller.ControllerContext.HttpContext.Request.Scheme = "https";
48+
_controller.ControllerContext.HttpContext.Request.Host = new HostString("test.example.com");
49+
}
50+
51+
[Fact]
52+
public async Task Auth_WithIdentifier_UsesProvidedIdentifier()
53+
{
54+
var result = await _controller.Auth("my-cluster");
55+
56+
var okResult = Assert.IsType<OkObjectResult>(result.Result);
57+
var model = Assert.IsType<NewAuthModel>(okResult.Value);
58+
Assert.Contains("my-cluster", model.LocalAuthModel.ConfigMap.ClusterIdentifier);
59+
}
60+
61+
[Fact]
62+
public async Task Auth_WithoutIdentifier_GeneratesGuidIdentifier()
63+
{
64+
var result = await _controller.Auth(null);
65+
66+
var okResult = Assert.IsType<OkObjectResult>(result.Result);
67+
var model = Assert.IsType<NewAuthModel>(okResult.Value);
68+
Assert.False(string.IsNullOrWhiteSpace(model.LocalAuthModel.ConfigMap.ClusterIdentifier));
69+
Assert.Contains("Authentication__ApiKeys__", model.LocalAuthModel.ConfigMap.ClusterIdentifier);
70+
}
71+
72+
[Fact]
73+
public async Task Auth_WithEmptyString_GeneratesGuidIdentifier()
74+
{
75+
var result = await _controller.Auth("");
76+
77+
var okResult = Assert.IsType<OkObjectResult>(result.Result);
78+
var model = Assert.IsType<NewAuthModel>(okResult.Value);
79+
Assert.False(string.IsNullOrWhiteSpace(model.LocalAuthModel.ConfigMap.ClusterIdentifier));
80+
}
81+
82+
[Fact]
83+
public async Task Auth_ReturnsCorrectNextIndex_WhenApiKeysExist()
84+
{
85+
var result = await _controller.Auth("test");
86+
87+
var okResult = Assert.IsType<OkObjectResult>(result.Result);
88+
var model = Assert.IsType<NewAuthModel>(okResult.Value);
89+
Assert.StartsWith("Authentication__ApiKeys__1__ClusterIdentifier:", model.LocalAuthModel.ConfigMap.ClusterIdentifier);
90+
}
91+
92+
[Fact]
93+
public async Task Auth_ReturnsCorrectNextIndex_WhenNoApiKeysExist()
94+
{
95+
var emptyAuthOptions = new ApiAuthenticationHandlerOptions { ApiKeys = Array.Empty<ApiKey>() };
96+
_authOptionsMock.Setup(x => x.Value).Returns(emptyAuthOptions);
97+
98+
var controller = new AuthenticationController(_authOptionsMock.Object, _hasher, _optionsMock.Object);
99+
controller.ControllerContext = new ControllerContext
100+
{
101+
HttpContext = new DefaultHttpContext()
102+
};
103+
controller.ControllerContext.HttpContext.Request.Scheme = "https";
104+
controller.ControllerContext.HttpContext.Request.Host = new HostString("test.example.com");
105+
106+
var result = await controller.Auth("test");
107+
108+
var okResult = Assert.IsType<OkObjectResult>(result.Result);
109+
var model = Assert.IsType<NewAuthModel>(okResult.Value);
110+
Assert.StartsWith("Authentication__ApiKeys__0__ClusterIdentifier:", model.LocalAuthModel.ConfigMap.ClusterIdentifier);
111+
}
112+
113+
[Fact]
114+
public async Task Auth_ReturnsRemoteAuthModel_WithCorrectClusterIdentifier()
115+
{
116+
var result = await _controller.Auth("test");
117+
118+
var okResult = Assert.IsType<OkObjectResult>(result.Result);
119+
var model = Assert.IsType<NewAuthModel>(okResult.Value);
120+
Assert.Contains("local-cluster", model.RemoteAuthModel.ConfigMap.ClusterIdentifier);
121+
}
122+
123+
[Fact]
124+
public async Task Auth_ReturnsRemoteAuthModel_WithCorrectUrl()
125+
{
126+
var result = await _controller.Auth("test");
127+
128+
var okResult = Assert.IsType<OkObjectResult>(result.Result);
129+
var model = Assert.IsType<NewAuthModel>(okResult.Value);
130+
Assert.Contains("https://test.example.com", model.RemoteAuthModel.ConfigMap.Url);
131+
}
132+
133+
[Fact]
134+
public async Task Auth_ReturnsLocalSecretHash_NonEmpty()
135+
{
136+
var result = await _controller.Auth("test");
137+
138+
var okResult = Assert.IsType<OkObjectResult>(result.Result);
139+
var model = Assert.IsType<NewAuthModel>(okResult.Value);
140+
Assert.False(string.IsNullOrWhiteSpace(model.LocalAuthModel.Secret.Hash));
141+
Assert.StartsWith("Authentication__ApiKeys__", model.LocalAuthModel.Secret.Hash);
142+
}
143+
144+
[Fact]
145+
public async Task Auth_ReturnsRemoteSecretKey_NonEmpty()
146+
{
147+
var result = await _controller.Auth("test");
148+
149+
var okResult = Assert.IsType<OkObjectResult>(result.Result);
150+
var model = Assert.IsType<NewAuthModel>(okResult.Value);
151+
Assert.False(string.IsNullOrWhiteSpace(model.RemoteAuthModel.Secret.Key));
152+
Assert.StartsWith("Peers__0__Key:", model.RemoteAuthModel.Secret.Key);
153+
}
154+
155+
[Fact]
156+
public async Task Salt_ReturnsOkResult_WithSaltModel()
157+
{
158+
var result = await _controller.Salt();
159+
160+
var okResult = Assert.IsType<OkObjectResult>(result.Result);
161+
var model = Assert.IsType<NewSaltModel>(okResult.Value);
162+
Assert.StartsWith("ClusterSalt:", model.Salt);
163+
}
164+
165+
[Fact]
166+
public async Task Salt_ReturnsSaltWithBase64Value()
167+
{
168+
var result = await _controller.Salt();
169+
170+
var okResult = Assert.IsType<OkObjectResult>(result.Result);
171+
var model = Assert.IsType<NewSaltModel>(okResult.Value);
172+
var saltValue = model.Salt.Replace("ClusterSalt: ", "");
173+
var exception = Record.Exception(() => Convert.FromBase64String(saltValue));
174+
Assert.Null(exception);
175+
}
176+
}
177+
}

0 commit comments

Comments
 (0)