diff --git a/examples/ConversionWebhookOperator/Program.cs b/examples/ConversionWebhookOperator/Program.cs
index 44a63cea..d317706a 100644
--- a/examples/ConversionWebhookOperator/Program.cs
+++ b/examples/ConversionWebhookOperator/Program.cs
@@ -1,6 +1,8 @@
using KubeOps.Operator;
using KubeOps.Operator.Web.Builder;
+#pragma warning disable CS0618 // Type or member is obsolete
+
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddKubernetesOperator()
diff --git a/examples/WebhookOperator/Program.cs b/examples/WebhookOperator/Program.cs
index 44a63cea..d317706a 100644
--- a/examples/WebhookOperator/Program.cs
+++ b/examples/WebhookOperator/Program.cs
@@ -1,6 +1,8 @@
using KubeOps.Operator;
using KubeOps.Operator.Web.Builder;
+#pragma warning disable CS0618 // Type or member is obsolete
+
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddKubernetesOperator()
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index edd3521c..20097967 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -36,15 +36,15 @@
-
+
diff --git a/src/KubeOps.Abstractions/Certificates/CertificatePair.cs b/src/KubeOps.Abstractions/Certificates/CertificatePair.cs
index 122fba70..b06eac24 100644
--- a/src/KubeOps.Abstractions/Certificates/CertificatePair.cs
+++ b/src/KubeOps.Abstractions/Certificates/CertificatePair.cs
@@ -1,7 +1,6 @@
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
-namespace KubeOps.Abstractions.Certificates
-{
- public record CertificatePair(X509Certificate2 Certificate, AsymmetricAlgorithm Key);
-}
+namespace KubeOps.Abstractions.Certificates;
+
+public record CertificatePair(X509Certificate2 Certificate, AsymmetricAlgorithm Key);
diff --git a/src/KubeOps.Abstractions/Certificates/ICertificateProvider.cs b/src/KubeOps.Abstractions/Certificates/ICertificateProvider.cs
index fd9d5cb1..5e74c1b4 100644
--- a/src/KubeOps.Abstractions/Certificates/ICertificateProvider.cs
+++ b/src/KubeOps.Abstractions/Certificates/ICertificateProvider.cs
@@ -1,22 +1,18 @@
-using System.Security.Cryptography;
-using System.Security.Cryptography.X509Certificates;
+namespace KubeOps.Abstractions.Certificates;
-namespace KubeOps.Abstractions.Certificates
+///
+/// Defines properties for certificate/key pair so a custom certificate/key provider may be implemented.
+/// The provider is used by the CertificateWebhookService to provide a caBundle to the webhooks.
+///
+public interface ICertificateProvider : IDisposable
{
///
- /// Defines properties for certificate/key pair so a custom certificate/key provider may be implemented.
- /// The provider is used by the CertificateWebhookService to provide a caBundle to the webhooks.
+ /// The server certificate and key.
///
- public interface ICertificateProvider : IDisposable
- {
- ///
- /// The server certificate and key.
- ///
- CertificatePair Server { get; }
+ CertificatePair Server { get; }
- ///
- /// The root certificate and key.
- ///
- CertificatePair Root { get; }
- }
+ ///
+ /// The root certificate and key.
+ ///
+ CertificatePair Root { get; }
}
diff --git a/src/KubeOps.Abstractions/KubeOps.Abstractions.csproj b/src/KubeOps.Abstractions/KubeOps.Abstractions.csproj
index 9f603584..5655fef2 100644
--- a/src/KubeOps.Abstractions/KubeOps.Abstractions.csproj
+++ b/src/KubeOps.Abstractions/KubeOps.Abstractions.csproj
@@ -14,7 +14,7 @@
-
+
diff --git a/src/KubeOps.Cli/Generators/CertificateGenerator.cs b/src/KubeOps.Cli/Generators/CertificateGenerator.cs
index 3eb720e1..a8f895c9 100644
--- a/src/KubeOps.Cli/Generators/CertificateGenerator.cs
+++ b/src/KubeOps.Cli/Generators/CertificateGenerator.cs
@@ -7,7 +7,7 @@ internal class CertificateGenerator(string serverName, string namespaceName) : I
{
public void Generate(ResultOutput output)
{
- using Operator.Web.CertificateGenerator generator = new(serverName, namespaceName);
+ using Operator.Web.Certificates.CertificateGenerator generator = new(serverName, namespaceName);
output.Add("ca.pem", generator.Root.Certificate.EncodeToPem(), OutputFormat.Plain);
output.Add("ca-key.pem", generator.Root.Key.EncodeToPem(), OutputFormat.Plain);
diff --git a/src/KubeOps.Cli/Generators/RbacGenerator.cs b/src/KubeOps.Cli/Generators/RbacGenerator.cs
index 30e74633..ffad903d 100644
--- a/src/KubeOps.Cli/Generators/RbacGenerator.cs
+++ b/src/KubeOps.Cli/Generators/RbacGenerator.cs
@@ -26,7 +26,7 @@ public void Generate(ResultOutput output)
var roleBinding = new V1ClusterRoleBinding(
roleRef: new V1RoleRef(V1ClusterRole.KubeGroup, V1ClusterRole.KubeKind, "operator-role"),
- subjects: new List
+ subjects: new List
{
new(V1ServiceAccount.KubeKind, "default", namespaceProperty: "system"),
})
diff --git a/src/KubeOps.Cli/KubeOps.Cli.csproj b/src/KubeOps.Cli/KubeOps.Cli.csproj
index 6f731eae..466315d1 100644
--- a/src/KubeOps.Cli/KubeOps.Cli.csproj
+++ b/src/KubeOps.Cli/KubeOps.Cli.csproj
@@ -18,14 +18,14 @@
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/src/KubeOps.Cli/Transpilation/BaseWebhook.cs b/src/KubeOps.Cli/Transpilation/BaseWebhook.cs
index 10155f61..eb0850c9 100644
--- a/src/KubeOps.Cli/Transpilation/BaseWebhook.cs
+++ b/src/KubeOps.Cli/Transpilation/BaseWebhook.cs
@@ -6,14 +6,14 @@ namespace KubeOps.Cli.Transpilation;
internal abstract record BaseWebhook(TypeInfo Webhook, EntityMetadata Metadata)
{
+ public abstract string WebhookPath { get; }
+
private bool HasCreate => Webhook.DeclaredMembers.Any(m => m.Name.StartsWith("Create"));
private bool HasUpdate => Webhook.DeclaredMembers.Any(m => m.Name.StartsWith("Update"));
private bool HasDelete => Webhook.DeclaredMembers.Any(m => m.Name.StartsWith("Delete"));
- public abstract string WebhookPath { get; }
-
public string[] GetOperations() =>
new[] { HasCreate ? "CREATE" : null, HasUpdate ? "UPDATE" : null, HasDelete ? "DELETE" : null, }
.Where(o => o is not null).ToArray()!;
diff --git a/src/KubeOps.Generator/KubeOps.Generator.csproj b/src/KubeOps.Generator/KubeOps.Generator.csproj
index 39531ed7..0612269e 100644
--- a/src/KubeOps.Generator/KubeOps.Generator.csproj
+++ b/src/KubeOps.Generator/KubeOps.Generator.csproj
@@ -18,11 +18,16 @@
-
+
-
+
diff --git a/src/KubeOps.Operator.Web/Certificates/CertificateExtensions.cs b/src/KubeOps.Operator.Web/Certificates/CertificateExtensions.cs
index 734e2d89..7310e6c2 100644
--- a/src/KubeOps.Operator.Web/Certificates/CertificateExtensions.cs
+++ b/src/KubeOps.Operator.Web/Certificates/CertificateExtensions.cs
@@ -4,55 +4,54 @@
using KubeOps.Abstractions.Certificates;
-namespace KubeOps.Operator.Web.Certificates
+namespace KubeOps.Operator.Web.Certificates;
+
+public static class CertificateExtensions
{
- public static class CertificateExtensions
- {
- ///
- /// Encodes the certificate in PEM format for use in Kubernetes.
- ///
- /// The certificate to encode.
- /// The byte representation of the PEM-encoded certificate.
- public static byte[] EncodeToPemBytes(this X509Certificate2 certificate) => Encoding.UTF8.GetBytes(certificate.EncodeToPem());
+ ///
+ /// Encodes the certificate in PEM format for use in Kubernetes.
+ ///
+ /// The certificate to encode.
+ /// The byte representation of the PEM-encoded certificate.
+ public static byte[] EncodeToPemBytes(this X509Certificate2 certificate) => Encoding.UTF8.GetBytes(certificate.EncodeToPem());
- ///
- /// Encodes the certificate in PEM format.
- ///
- /// The certificate to encode.
- /// The string representation of the PEM-encoded certificate.
- public static string EncodeToPem(this X509Certificate2 certificate) => new(PemEncoding.Write("CERTIFICATE", certificate.RawData));
+ ///
+ /// Encodes the certificate in PEM format.
+ ///
+ /// The certificate to encode.
+ /// The string representation of the PEM-encoded certificate.
+ public static string EncodeToPem(this X509Certificate2 certificate) => new(PemEncoding.Write("CERTIFICATE", certificate.RawData));
- ///
- /// Encodes the key in PEM format.
- ///
- /// The key to encode.
- /// The string representation of the PEM-encoded key.
- public static string EncodeToPem(this AsymmetricAlgorithm key) => new(PemEncoding.Write("PRIVATE KEY", key.ExportPkcs8PrivateKey()));
+ ///
+ /// Encodes the key in PEM format.
+ ///
+ /// The key to encode.
+ /// The string representation of the PEM-encoded key.
+ public static string EncodeToPem(this AsymmetricAlgorithm key) => new(PemEncoding.Write("PRIVATE KEY", key.ExportPkcs8PrivateKey()));
- ///
- /// Generates a new server certificate with its private key attached, and sets .
- /// For example, this certificate can be used in development environments to configure .
- ///
- /// The cert/key tuple to attach.
- /// An with the private key attached.
- /// The not have a CopyWithPrivateKey method, or the
- /// method has not been implemented in this extension.
- public static X509Certificate2 CopyServerCertWithPrivateKey(this CertificatePair serverPair)
+ ///
+ /// Generates a new server certificate with its private key attached, and sets .
+ /// For example, this certificate can be used in development environments to configure .
+ ///
+ /// The cert/key tuple to attach.
+ /// An with the private key attached.
+ /// The does not have a CopyWithPrivateKey method, or the
+ /// method has not been implemented in this extension.
+ public static X509Certificate2 CopyServerCertWithPrivateKey(this CertificatePair serverPair)
+ {
+ const string? password = null;
+ using X509Certificate2 temp = serverPair.Key switch
{
- const string? password = null;
- using X509Certificate2 temp = serverPair.Key switch
- {
- ECDsa ecdsa => serverPair.Certificate.CopyWithPrivateKey(ecdsa),
- RSA rsa => serverPair.Certificate.CopyWithPrivateKey(rsa),
- ECDiffieHellman ecdh => serverPair.Certificate.CopyWithPrivateKey(ecdh),
- DSA dsa => serverPair.Certificate.CopyWithPrivateKey(dsa),
- _ => throw new NotImplementedException($"{serverPair.Key} is not implemented for {nameof(CopyServerCertWithPrivateKey)}"),
- };
+ ECDsa ecdsa => serverPair.Certificate.CopyWithPrivateKey(ecdsa),
+ RSA rsa => serverPair.Certificate.CopyWithPrivateKey(rsa),
+ ECDiffieHellman ecdh => serverPair.Certificate.CopyWithPrivateKey(ecdh),
+ DSA dsa => serverPair.Certificate.CopyWithPrivateKey(dsa),
+ _ => throw new NotImplementedException($"{serverPair.Key} is not implemented for {nameof(CopyServerCertWithPrivateKey)}"),
+ };
- return new X509Certificate2(
- temp.Export(X509ContentType.Pfx, password),
- password,
- X509KeyStorageFlags.Exportable | X509KeyStorageFlags.PersistKeySet);
- }
+ return new X509Certificate2(
+ temp.Export(X509ContentType.Pfx, password),
+ password,
+ X509KeyStorageFlags.Exportable | X509KeyStorageFlags.PersistKeySet);
}
}
diff --git a/src/KubeOps.Operator.Web/Certificates/CertificateGenerator.cs b/src/KubeOps.Operator.Web/Certificates/CertificateGenerator.cs
index 28e272d8..27a69c2d 100644
--- a/src/KubeOps.Operator.Web/Certificates/CertificateGenerator.cs
+++ b/src/KubeOps.Operator.Web/Certificates/CertificateGenerator.cs
@@ -5,213 +5,212 @@
using KubeOps.Abstractions.Certificates;
-namespace KubeOps.Operator.Web
+namespace KubeOps.Operator.Web.Certificates;
+
+///
+/// Generates a self-signed CA certificate and server certificate using ECDsa that can be used for operator webhooks.
+///
+public class CertificateGenerator : ICertificateProvider
{
+ private readonly string _serverName;
+ private readonly string? _serverNamespace;
+ private readonly DateTime _startDate;
+ private readonly DateTime _endDate;
+ private readonly Lazy _root;
+ private readonly Lazy _server;
+
///
- /// Generates a self-signed CA certificate and server certificate using ECDsa that can be used for operator webhooks.
+ /// Initializes a new instance of the class.
///
- public class CertificateGenerator : ICertificateProvider
+ /// The hostname, IP, or FQDN of the machine running the operator.
+ public CertificateGenerator(string serverName)
{
- private readonly string _serverName;
- private readonly string? _serverNamespace;
- private readonly DateTime _startDate;
- private readonly DateTime _endDate;
- private readonly Lazy _root;
- private readonly Lazy _server;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The hostname, IP, or FQDN of the machine running the operator.
- public CertificateGenerator(string serverName)
- {
- _serverName = serverName;
- _serverNamespace = null;
- _startDate = DateTime.UtcNow.Date;
- _endDate = _startDate.AddYears(5);
- _root = new(GenerateRootCertificate);
- _server = new(GenerateServerCertificate);
- }
+ _serverName = serverName;
+ _serverNamespace = null;
+ _startDate = DateTime.UtcNow.Date;
+ _endDate = _startDate.AddYears(5);
+ _root = new(GenerateRootCertificate);
+ _server = new(GenerateServerCertificate);
+ }
- ///
- ///
- ///
- ///
- /// The Kubernetes namespace the server will run in.
- public CertificateGenerator(string serverName, string serverNamespace)
- : this(serverName)
- {
- _serverNamespace = serverNamespace;
- }
+ ///
+ ///
+ ///
+ ///
+ /// The Kubernetes namespace the server will run in.
+ public CertificateGenerator(string serverName, string serverNamespace)
+ : this(serverName)
+ {
+ _serverNamespace = serverNamespace;
+ }
+
+ public CertificatePair Root => _root.Value;
- public CertificatePair Root => _root.Value;
+ public CertificatePair Server => _server.Value;
- public CertificatePair Server => _server.Value;
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
- public void Dispose()
+ protected virtual void Dispose(bool disposing)
+ {
+ if (_root.IsValueCreated)
{
- Dispose(true);
- GC.SuppressFinalize(this);
+ _root.Value.Certificate.Dispose();
+ _root.Value.Key.Dispose();
}
- protected virtual void Dispose(bool disposing)
+ if (_server.IsValueCreated)
{
- if (_root.IsValueCreated)
- {
- _root.Value.Certificate.Dispose();
- _root.Value.Key.Dispose();
- }
+ _server.Value.Certificate.Dispose();
+ _server.Value.Key.Dispose();
+ }
+ }
- if (_server.IsValueCreated)
- {
- _server.Value.Certificate.Dispose();
- _server.Value.Key.Dispose();
- }
+ private CertificatePair GenerateRootCertificate()
+ {
+ ECDsa? key = null;
+ X509Certificate2? cert = null;
+ try
+ {
+ // Create an ECDsa key and a certificate request
+ key = ECDsa.Create(ECCurve.NamedCurves.nistP256);
+ var request = new CertificateRequest(
+ "CN=Operator Root CA, C=DEV, L=Kubernetes",
+ key,
+ HashAlgorithmName.SHA512);
+
+ // Specify certain details of how the certificate can be used
+ request.CertificateExtensions.Add(
+ new X509BasicConstraintsExtension(true, false, 0, true));
+ request.CertificateExtensions.Add(
+ new X509KeyUsageExtension(
+ X509KeyUsageFlags.KeyCertSign | X509KeyUsageFlags.CrlSign | X509KeyUsageFlags.KeyEncipherment,
+ true));
+
+ // Create the self-signed cert
+ cert = request.CreateSelfSigned(_startDate, _endDate);
+ return new(cert, key);
}
+ catch
+ {
+ key?.Dispose();
+ cert?.Dispose();
+ throw;
+ }
+ }
- private CertificatePair GenerateRootCertificate()
+ private CertificatePair GenerateServerCertificate()
+ {
+ ECDsa? key = null;
+ X509Certificate2? cert = null;
+ try
{
- ECDsa? key = null;
- X509Certificate2? cert = null;
- try
+ key = ECDsa.Create(ECCurve.NamedCurves.nistP256);
+ var request = new CertificateRequest(
+ "CN=Operator Service, C=DEV, L=Kubernetes",
+ key,
+ HashAlgorithmName.SHA512);
+
+ request.CertificateExtensions.Add(
+ new X509BasicConstraintsExtension(false, false, 0, false));
+ request.CertificateExtensions.Add(
+ new X509KeyUsageExtension(
+ X509KeyUsageFlags.NonRepudiation | X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.KeyEncipherment,
+ true));
+
+ // Key purpose: clientAuth and serverAuth
+ request.CertificateExtensions.Add(
+ new X509EnhancedKeyUsageExtension(
+ [new Oid("1.3.6.1.5.5.7.3.1"), new Oid("1.3.6.1.5.5.7.3.2")],
+ false));
+ request.CertificateExtensions.Add(
+ new X509SubjectKeyIdentifierExtension(
+ Root.Certificate.PublicKey,
+ false));
+ request.CertificateExtensions.Add(
+ new CustomX509AuthorityKeyIdentifierExtension(
+ Root.Certificate,
+ false));
+
+ // If a server namespace is provided, it's safe to assume that the operator will be running in the Kubernetes cluster
+ // Otherwise, just try to parse whatever is there (i.e. for development)
+ var sanBuilder = new SubjectAlternativeNameBuilder();
+ if (_serverNamespace != null)
{
- // Create an ECDsa key and a certificate request
- key = ECDsa.Create(ECCurve.NamedCurves.nistP256);
- var request = new CertificateRequest(
- "CN=Operator Root CA, C=DEV, L=Kubernetes",
- key,
- HashAlgorithmName.SHA512);
-
- // Specify certain details of how the certificate can be used
- request.CertificateExtensions.Add(
- new X509BasicConstraintsExtension(true, false, 0, true));
- request.CertificateExtensions.Add(
- new X509KeyUsageExtension(
- X509KeyUsageFlags.KeyCertSign | X509KeyUsageFlags.CrlSign | X509KeyUsageFlags.KeyEncipherment,
- true));
-
- // Create the self-signed cert
- cert = request.CreateSelfSigned(_startDate, _endDate);
- return new(cert, key);
+ sanBuilder.AddDnsName($"{_serverName}.{_serverNamespace}.svc");
+ sanBuilder.AddDnsName($"*.{_serverNamespace}.svc");
+ sanBuilder.AddDnsName("*.svc");
}
- catch
+ else if (IPAddress.TryParse(_serverName, out IPAddress? ipAddress))
{
- key?.Dispose();
- cert?.Dispose();
- throw;
+ sanBuilder.AddIpAddress(ipAddress);
}
- }
-
- private CertificatePair GenerateServerCertificate()
- {
- ECDsa? key = null;
- X509Certificate2? cert = null;
- try
+ else
{
- key = ECDsa.Create(ECCurve.NamedCurves.nistP256);
- var request = new CertificateRequest(
- "CN=Operator Service, C=DEV, L=Kubernetes",
- key,
- HashAlgorithmName.SHA512);
-
- request.CertificateExtensions.Add(
- new X509BasicConstraintsExtension(false, false, 0, false));
- request.CertificateExtensions.Add(
- new X509KeyUsageExtension(
- X509KeyUsageFlags.NonRepudiation | X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.KeyEncipherment,
- true));
-
- // Key purpose: clientAuth and serverAuth
- request.CertificateExtensions.Add(
- new X509EnhancedKeyUsageExtension(
- [new Oid("1.3.6.1.5.5.7.3.1"), new Oid("1.3.6.1.5.5.7.3.2")],
- false));
- request.CertificateExtensions.Add(
- new X509SubjectKeyIdentifierExtension(
- Root.Certificate.PublicKey,
- false));
- request.CertificateExtensions.Add(
- new CustomX509AuthorityKeyIdentifierExtension(
- Root.Certificate,
- false));
-
- // If a server namespace is provided, it's safe to assume that the operator will be running in the Kubernetes cluster
- // Otherwise, just try to parse whatever is there (i.e. for development)
- var sanBuilder = new SubjectAlternativeNameBuilder();
- if (_serverNamespace != null)
- {
- sanBuilder.AddDnsName($"{_serverName}.{_serverNamespace}.svc");
- sanBuilder.AddDnsName($"*.{_serverNamespace}.svc");
- sanBuilder.AddDnsName("*.svc");
- }
- else if (IPAddress.TryParse(_serverName, out IPAddress? ipAddress))
- {
- sanBuilder.AddIpAddress(ipAddress);
- }
- else
- {
- sanBuilder.AddDnsName(_serverName);
- }
+ sanBuilder.AddDnsName(_serverName);
+ }
- request.CertificateExtensions.Add(sanBuilder.Build());
+ request.CertificateExtensions.Add(sanBuilder.Build());
- // Generate using the root certificate
- X509SignatureGenerator generator = X509SignatureGenerator
- .CreateForECDsa(Root.Certificate.GetECDsaPrivateKey()!);
+ // Generate using the root certificate
+ X509SignatureGenerator generator = X509SignatureGenerator
+ .CreateForECDsa(Root.Certificate.GetECDsaPrivateKey()!);
- // Generate
- cert = request.Create(
- Root.Certificate.SubjectName,
- generator,
- _startDate,
- _endDate,
- Guid.NewGuid().ToByteArray());
+ // Generate
+ cert = request.Create(
+ Root.Certificate.SubjectName,
+ generator,
+ _startDate,
+ _endDate,
+ Guid.NewGuid().ToByteArray());
- return new(cert, key);
- }
- catch
- {
- key?.Dispose();
- cert?.Dispose();
- throw;
- }
+ return new(cert, key);
}
-
- ///
- /// Custom class for implementing a slim version of the .NET7/8 X509AuthorityKeyIdentifierExtension class.
- ///
- private sealed class CustomX509AuthorityKeyIdentifierExtension(X509Certificate2 certificate, bool critical)
- : X509Extension(new Oid("2.5.29.35"), CreateFromCertificate(certificate), critical)
+ catch
{
- // https://source.dot.net/#System.Security.Cryptography/System/Security/Cryptography/X509Certificates/X509AuthorityKeyIdentifierExtension.cs
- // This .NET code is shipped with .NET 7/8, but is not in .NET 6, which is still supported by operator
- // The method below uses portions of the static methods CreateFromCertificate() and Create()
- private static byte[] CreateFromCertificate(X509Certificate2 certificate)
- {
- X509SubjectKeyIdentifierExtension skid =
- (X509SubjectKeyIdentifierExtension?)certificate.Extensions["2.5.29.14"] ??
- new X509SubjectKeyIdentifierExtension(certificate.PublicKey, false);
+ key?.Dispose();
+ cert?.Dispose();
+ throw;
+ }
+ }
- byte[] skidBytes = Convert.FromHexString(skid.SubjectKeyIdentifier!);
+ ///
+ /// Custom class for implementing a slim version of the .NET7/8 X509AuthorityKeyIdentifierExtension class.
+ ///
+ private sealed class CustomX509AuthorityKeyIdentifierExtension(X509Certificate2 certificate, bool critical)
+ : X509Extension(new Oid("2.5.29.35"), CreateFromCertificate(certificate), critical)
+ {
+ // https://source.dot.net/#System.Security.Cryptography/System/Security/Cryptography/X509Certificates/X509AuthorityKeyIdentifierExtension.cs
+ // This .NET code is shipped with .NET 7/8, but is not in .NET 6, which is still supported by operator
+ // The method below uses portions of the static methods CreateFromCertificate() and Create()
+ private static byte[] CreateFromCertificate(X509Certificate2 certificate)
+ {
+ X509SubjectKeyIdentifierExtension skid =
+ (X509SubjectKeyIdentifierExtension?)certificate.Extensions["2.5.29.14"] ??
+ new X509SubjectKeyIdentifierExtension(certificate.PublicKey, false);
- AsnWriter writer = new(AsnEncodingRules.DER);
+ byte[] skidBytes = Convert.FromHexString(skid.SubjectKeyIdentifier!);
- using (writer.PushSequence())
- {
- writer.WriteOctetString(skidBytes, new Asn1Tag(TagClass.ContextSpecific, 0));
+ AsnWriter writer = new(AsnEncodingRules.DER);
- using (writer.PushSequence(new Asn1Tag(TagClass.ContextSpecific, 1)))
- using (writer.PushSequence(new Asn1Tag(TagClass.ContextSpecific, 4)))
- {
- writer.WriteEncodedValue(certificate.IssuerName.RawData);
- }
+ using (writer.PushSequence())
+ {
+ writer.WriteOctetString(skidBytes, new Asn1Tag(TagClass.ContextSpecific, 0));
- byte[] serialBytes = Convert.FromHexString(certificate.SerialNumber);
- writer.WriteInteger(serialBytes, new Asn1Tag(TagClass.ContextSpecific, 2));
+ using (writer.PushSequence(new Asn1Tag(TagClass.ContextSpecific, 1)))
+ using (writer.PushSequence(new Asn1Tag(TagClass.ContextSpecific, 4)))
+ {
+ writer.WriteEncodedValue(certificate.IssuerName.RawData);
}
- return writer.Encode();
+ byte[] serialBytes = Convert.FromHexString(certificate.SerialNumber);
+ writer.WriteInteger(serialBytes, new Asn1Tag(TagClass.ContextSpecific, 2));
}
+
+ return writer.Encode();
}
}
}
diff --git a/src/KubeOps.Operator.Web/Certificates/CertificateWebhookService.cs b/src/KubeOps.Operator.Web/Certificates/CertificateWebhookService.cs
index e6acf069..6eb96b59 100644
--- a/src/KubeOps.Operator.Web/Certificates/CertificateWebhookService.cs
+++ b/src/KubeOps.Operator.Web/Certificates/CertificateWebhookService.cs
@@ -5,26 +5,25 @@
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
-namespace KubeOps.Operator.Web.Certificates
+namespace KubeOps.Operator.Web.Certificates;
+
+internal class CertificateWebhookService(ILogger logger, IKubernetesClient client, WebhookLoader loader, WebhookConfig config, ICertificateProvider provider)
+ : WebhookServiceBase(client, loader, config), IHostedService
{
- internal class CertificateWebhookService(ILogger logger, IKubernetesClient client, WebhookLoader loader, WebhookConfig config, ICertificateProvider provider)
- : WebhookServiceBase(client, loader, config), IHostedService
- {
- private readonly ILogger _logger = logger;
- private readonly ICertificateProvider _provider = provider;
+ private readonly ILogger _logger = logger;
+ private readonly ICertificateProvider _provider = provider;
- public async Task StartAsync(CancellationToken cancellationToken)
- {
- CaBundle = _provider.Server.Certificate.EncodeToPemBytes();
+ public async Task StartAsync(CancellationToken cancellationToken)
+ {
+ CaBundle = _provider.Server.Certificate.EncodeToPemBytes();
- _logger.LogDebug("Registering webhooks");
- await RegisterAll();
- }
+ _logger.LogDebug("Registering webhooks");
+ await RegisterAll();
+ }
- public Task StopAsync(CancellationToken cancellationToken)
- {
- _provider.Dispose();
- return Task.CompletedTask;
- }
+ public Task StopAsync(CancellationToken cancellationToken)
+ {
+ _provider.Dispose();
+ return Task.CompletedTask;
}
}
diff --git a/src/KubeOps.Operator.Web/KubeOps.Operator.Web.csproj b/src/KubeOps.Operator.Web/KubeOps.Operator.Web.csproj
index 018ee0cc..1a628439 100644
--- a/src/KubeOps.Operator.Web/KubeOps.Operator.Web.csproj
+++ b/src/KubeOps.Operator.Web/KubeOps.Operator.Web.csproj
@@ -27,7 +27,7 @@
-
+
diff --git a/src/KubeOps.Operator.Web/LocalTunnel/TunnelWebhookService.cs b/src/KubeOps.Operator.Web/LocalTunnel/TunnelWebhookService.cs
index b5a7ebb3..a7e40842 100644
--- a/src/KubeOps.Operator.Web/LocalTunnel/TunnelWebhookService.cs
+++ b/src/KubeOps.Operator.Web/LocalTunnel/TunnelWebhookService.cs
@@ -5,26 +5,27 @@
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
-namespace KubeOps.Operator.Web.LocalTunnel
+namespace KubeOps.Operator.Web.LocalTunnel;
+
+internal class TunnelWebhookService(
+ ILogger logger,
+ IKubernetesClient client,
+ WebhookLoader loader,
+ WebhookConfig config,
+ DevelopmentTunnel developmentTunnel)
+ : WebhookServiceBase(client, loader, config), IHostedService
{
- internal class TunnelWebhookService(ILogger logger, IKubernetesClient client, WebhookLoader loader, WebhookConfig config, DevelopmentTunnel developmentTunnel)
- : WebhookServiceBase(client, loader, config), IHostedService
+ public async Task StartAsync(CancellationToken cancellationToken)
{
- private readonly ILogger _logger = logger;
- private readonly DevelopmentTunnel _developmentTunnel = developmentTunnel;
-
- public async Task StartAsync(CancellationToken cancellationToken)
- {
- Uri = await _developmentTunnel.StartAsync(cancellationToken);
+ Uri = await developmentTunnel.StartAsync(cancellationToken);
- _logger.LogDebug("Registering webhooks");
- await RegisterAll();
- }
+ logger.LogDebug("Registering webhooks");
+ await RegisterAll();
+ }
- public Task StopAsync(CancellationToken cancellationToken)
- {
- _developmentTunnel.Dispose();
- return Task.CompletedTask;
- }
+ public Task StopAsync(CancellationToken cancellationToken)
+ {
+ developmentTunnel.Dispose();
+ return Task.CompletedTask;
}
}
diff --git a/src/KubeOps.Operator.Web/Webhooks/Admission/AdmissionRequest.cs b/src/KubeOps.Operator.Web/Webhooks/Admission/AdmissionRequest.cs
index e43ae9c1..26bbe515 100644
--- a/src/KubeOps.Operator.Web/Webhooks/Admission/AdmissionRequest.cs
+++ b/src/KubeOps.Operator.Web/Webhooks/Admission/AdmissionRequest.cs
@@ -1,4 +1,5 @@
-using System.Text.Json.Serialization;
+using System.ComponentModel.DataAnnotations;
+using System.Text.Json.Serialization;
using k8s;
using k8s.Models;
@@ -18,6 +19,7 @@ public sealed class AdmissionRequest : AdmissionReview
/// Admission request data.
///
[JsonPropertyName("request")]
+ [Required]
public AdmissionRequestData Request { get; init; } = new();
///
@@ -31,6 +33,7 @@ public sealed class AdmissionRequestData
/// The unique ID of the admission request.
///
[JsonPropertyName("uid")]
+ [Required]
public string Uid { get; init; } = string.Empty;
///
@@ -39,6 +42,7 @@ public sealed class AdmissionRequestData
/// "CONNECT" does exist, but is not supported by the operator-sdk.
///
[JsonPropertyName("operation")]
+ [Required]
public string Operation { get; init; } = string.Empty;
///
@@ -59,6 +63,7 @@ public sealed class AdmissionRequestData
/// A flag to indicate if the API was called with the "dryRun" flag.
///
[JsonPropertyName("dryRun")]
+ [Required]
public bool DryRun { get; init; }
}
}
diff --git a/src/KubeOps.Operator.Web/Webhooks/Admission/Mutation/MutationResult.cs b/src/KubeOps.Operator.Web/Webhooks/Admission/Mutation/MutationResult.cs
index d37f65fb..eb48944d 100644
--- a/src/KubeOps.Operator.Web/Webhooks/Admission/Mutation/MutationResult.cs
+++ b/src/KubeOps.Operator.Web/Webhooks/Admission/Mutation/MutationResult.cs
@@ -18,10 +18,6 @@ public record MutationResult(TEntity? ModifiedObject = default) : IActi
{
private const string JsonPatch = "JSONPatch";
- internal string Uid { get; init; } = string.Empty;
-
- internal JsonNode? OriginalObject { get; init; }
-
public bool Valid { get; init; } = true;
///
@@ -42,6 +38,10 @@ public record MutationResult(TEntity? ModifiedObject = default) : IActi
///
public IList Warnings { get; init; } = new List();
+ internal string Uid { get; init; } = string.Empty;
+
+ internal JsonNode? OriginalObject { get; init; }
+
///
public async Task ExecuteResultAsync(ActionContext context)
{
diff --git a/src/KubeOps.Operator.Web/Webhooks/Admission/Validation/ValidationResult.cs b/src/KubeOps.Operator.Web/Webhooks/Admission/Validation/ValidationResult.cs
index 65e8f738..33fcea43 100644
--- a/src/KubeOps.Operator.Web/Webhooks/Admission/Validation/ValidationResult.cs
+++ b/src/KubeOps.Operator.Web/Webhooks/Admission/Validation/ValidationResult.cs
@@ -9,8 +9,6 @@ namespace KubeOps.Operator.Web.Webhooks.Admission.Validation;
/// Whether the validation / the entity is valid or not.
public record ValidationResult(bool Valid = true) : IActionResult
{
- internal string Uid { get; init; } = string.Empty;
-
///
/// Provides additional information to the validation result.
/// The message is displayed to the user if the validation fails.
@@ -29,6 +27,8 @@ public record ValidationResult(bool Valid = true) : IActionResult
///
public IList Warnings { get; init; } = new List();
+ internal string Uid { get; init; } = string.Empty;
+
///
public async Task ExecuteResultAsync(ActionContext context)
{
diff --git a/src/KubeOps.Operator.Web/Webhooks/Conversion/ConversionRequest.cs b/src/KubeOps.Operator.Web/Webhooks/Conversion/ConversionRequest.cs
index f239319e..bb1df845 100644
--- a/src/KubeOps.Operator.Web/Webhooks/Conversion/ConversionRequest.cs
+++ b/src/KubeOps.Operator.Web/Webhooks/Conversion/ConversionRequest.cs
@@ -1,10 +1,8 @@
-using System.Diagnostics.CodeAnalysis;
+using System.ComponentModel.DataAnnotations;
using System.Runtime.Versioning;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
-using KubeOps.Operator.Web.Webhooks.Admission;
-
namespace KubeOps.Operator.Web.Webhooks.Conversion;
///
@@ -19,6 +17,7 @@ public sealed class ConversionRequest : ConversionReview
/// Conversion request data.
///
[JsonPropertyName("request")]
+ [Required]
public ConversionRequestData Request { get; init; } = new();
///
@@ -30,18 +29,20 @@ public sealed class ConversionRequestData
/// The unique ID of the conversion request.
///
[JsonPropertyName("uid")]
+ [Required]
public string Uid { get; init; } = string.Empty;
///
/// Target group/api version of the conversion.
///
[JsonPropertyName("desiredAPIVersion")]
+ [Required]
public string DesiredApiVersion { get; init; } = string.Empty;
///
/// List of objects that need to be converted.
///
[JsonPropertyName("objects")]
- public JsonNode[] Objects { get; init; } = Array.Empty();
+ public JsonNode[] Objects { get; init; } = [];
}
}
diff --git a/src/KubeOps.Operator.Web/Webhooks/WebhookServiceBase.cs b/src/KubeOps.Operator.Web/Webhooks/WebhookServiceBase.cs
index e6d7b793..1d416661 100644
--- a/src/KubeOps.Operator.Web/Webhooks/WebhookServiceBase.cs
+++ b/src/KubeOps.Operator.Web/Webhooks/WebhookServiceBase.cs
@@ -4,79 +4,79 @@
using KubeOps.KubernetesClient;
using KubeOps.Transpiler;
-namespace KubeOps.Operator.Web.Webhooks
+namespace KubeOps.Operator.Web.Webhooks;
+
+internal abstract class WebhookServiceBase(IKubernetesClient client, WebhookLoader loader, WebhookConfig config)
{
- internal abstract class WebhookServiceBase(IKubernetesClient client, WebhookLoader loader, WebhookConfig config)
- {
- ///
- /// The URI the webhooks will use to connect to the operator.
- ///
- private protected virtual Uri Uri { get; set; } = new($"https://{config.Hostname}:{config.Port}");
+ ///
+ /// The URI the webhooks will use to connect to the operator.
+ ///
+ private protected virtual Uri Uri { get; set; } = new($"https://{config.Hostname}:{config.Port}");
+
+ private protected IKubernetesClient Client { get; } = client;
- private protected IKubernetesClient Client { get; } = client;
+ ///
+ /// The PEM-encoded CA bundle for validating the webhook's certificate.
+ ///
+ private protected byte[]? CaBundle { get; set; }
- ///
- /// The PEM-encoded CA bundle for validating the webhook's certificate.
- ///
- private protected byte[]? CaBundle { get; set; }
+ internal async Task RegisterAll()
+ {
+ await RegisterValidators();
+ await RegisterMutators();
+ await RegisterConverters();
+ }
- internal async Task RegisterAll()
+ internal async Task RegisterConverters()
+ {
+ var conversionWebhooks = loader.ConversionWebhooks.ToList();
+ if (conversionWebhooks.Count == 0)
{
- await RegisterValidators();
- await RegisterMutators();
- await RegisterConverters();
+ return;
}
- internal async Task RegisterConverters()
+ foreach (var wh in conversionWebhooks)
{
- var conversionWebhooks = loader.ConversionWebhooks.ToList();
- if (conversionWebhooks.Count == 0)
+ var metadata = Entities.ToEntityMetadata(wh.BaseType!.GenericTypeArguments[0]).Metadata;
+ var crdName = $"{metadata.PluralName}.{metadata.Group}";
+
+ if (await Client.GetAsync(crdName) is not { } crd)
{
- return;
+ continue;
}
- foreach (var wh in conversionWebhooks)
+ var whUrl = $"{Uri}convert/{metadata.Group}/{metadata.PluralName}";
+ crd.Spec.Conversion = new V1CustomResourceConversion("Webhook")
{
- var metadata = Entities.ToEntityMetadata(wh.BaseType!.GenericTypeArguments[0]).Metadata;
- var crdName = $"{metadata.PluralName}.{metadata.Group}";
-
- if (await Client.GetAsync(crdName) is not { } crd)
- {
- continue;
- }
-
- var whUrl = $"{Uri}convert/{metadata.Group}/{metadata.PluralName}";
- crd.Spec.Conversion = new V1CustomResourceConversion("Webhook")
+ Webhook = new V1WebhookConversion
{
- Webhook = new V1WebhookConversion
+ ConversionReviewVersions = new[] { "v1" },
+ ClientConfig = new Apiextensionsv1WebhookClientConfig
{
- ConversionReviewVersions = new[] { "v1" },
- ClientConfig = new Apiextensionsv1WebhookClientConfig
- {
- Url = whUrl,
- CaBundle = CaBundle,
- },
+ Url = whUrl,
+ CaBundle = CaBundle,
},
- };
+ },
+ };
- await Client.UpdateAsync(crd);
- }
+ await Client.UpdateAsync(crd);
}
+ }
- internal async Task RegisterMutators()
- {
- var mutationWebhooks = loader
- .MutationWebhooks
- .Select(t => (HookTypeName: t.BaseType!.GenericTypeArguments[0].Name.ToLowerInvariant(),
- Entities.ToEntityMetadata(t.BaseType!.GenericTypeArguments[0]).Metadata))
- .Select(hook => new V1MutatingWebhook
+ internal async Task RegisterMutators()
+ {
+ var mutationWebhooks = loader
+ .MutationWebhooks
+ .Select(t => (HookTypeName: t.BaseType!.GenericTypeArguments[0].Name.ToLowerInvariant(),
+ Entities.ToEntityMetadata(t.BaseType!.GenericTypeArguments[0]).Metadata))
+ .Select(hook => new V1MutatingWebhook
+ {
+ Name = $"mutate.{hook.Metadata.SingularName}.{hook.Metadata.Group}.{hook.Metadata.Version}",
+ MatchPolicy = "Exact",
+ AdmissionReviewVersions = new[] { "v1" },
+ SideEffects = "None",
+ Rules = new[]
{
- Name = $"mutate.{hook.Metadata.SingularName}.{hook.Metadata.Group}.{hook.Metadata.Version}",
- MatchPolicy = "Exact",
- AdmissionReviewVersions = new[] { "v1" },
- SideEffects = "None",
- Rules = new[]
- {
new V1RuleWithOperations
{
Operations = new[] { "*" },
@@ -84,38 +84,38 @@ internal async Task RegisterMutators()
ApiGroups = new[] { hook.Metadata.Group },
ApiVersions = new[] { hook.Metadata.Version },
},
- },
- ClientConfig = new Admissionregistrationv1WebhookClientConfig
- {
- Url = $"{Uri}mutate/{hook.HookTypeName}",
- CaBundle = CaBundle,
- },
- });
+ },
+ ClientConfig = new Admissionregistrationv1WebhookClientConfig
+ {
+ Url = $"{Uri}mutate/{hook.HookTypeName}",
+ CaBundle = CaBundle,
+ },
+ });
- var mutatorConfig = new V1MutatingWebhookConfiguration(
- metadata: new V1ObjectMeta(name: "dev-mutators"),
- webhooks: mutationWebhooks.ToList()).Initialize();
+ var mutatorConfig = new V1MutatingWebhookConfiguration(
+ metadata: new V1ObjectMeta(name: "dev-mutators"),
+ webhooks: mutationWebhooks.ToList()).Initialize();
- if (mutatorConfig.Webhooks.Any())
- {
- await Client.SaveAsync(mutatorConfig);
- }
+ if (mutatorConfig.Webhooks.Any())
+ {
+ await Client.SaveAsync(mutatorConfig);
}
+ }
- internal async Task RegisterValidators()
- {
- var validationWebhooks = loader
- .ValidationWebhooks
- .Select(t => (HookTypeName: t.BaseType!.GenericTypeArguments[0].Name.ToLowerInvariant(),
- Entities.ToEntityMetadata(t.BaseType!.GenericTypeArguments[0]).Metadata))
- .Select(hook => new V1ValidatingWebhook
+ internal async Task RegisterValidators()
+ {
+ var validationWebhooks = loader
+ .ValidationWebhooks
+ .Select(t => (HookTypeName: t.BaseType!.GenericTypeArguments[0].Name.ToLowerInvariant(),
+ Entities.ToEntityMetadata(t.BaseType!.GenericTypeArguments[0]).Metadata))
+ .Select(hook => new V1ValidatingWebhook
+ {
+ Name = $"validate.{hook.Metadata.SingularName}.{hook.Metadata.Group}.{hook.Metadata.Version}",
+ MatchPolicy = "Exact",
+ AdmissionReviewVersions = new[] { "v1" },
+ SideEffects = "None",
+ Rules = new[]
{
- Name = $"validate.{hook.Metadata.SingularName}.{hook.Metadata.Group}.{hook.Metadata.Version}",
- MatchPolicy = "Exact",
- AdmissionReviewVersions = new[] { "v1" },
- SideEffects = "None",
- Rules = new[]
- {
new V1RuleWithOperations
{
Operations = new[] { "*" },
@@ -123,22 +123,21 @@ internal async Task RegisterValidators()
ApiGroups = new[] { hook.Metadata.Group },
ApiVersions = new[] { hook.Metadata.Version },
},
- },
- ClientConfig = new Admissionregistrationv1WebhookClientConfig
- {
- Url = $"{Uri}validate/{hook.HookTypeName}",
- CaBundle = CaBundle,
- },
- });
+ },
+ ClientConfig = new Admissionregistrationv1WebhookClientConfig
+ {
+ Url = $"{Uri}validate/{hook.HookTypeName}",
+ CaBundle = CaBundle,
+ },
+ });
- var validatorConfig = new V1ValidatingWebhookConfiguration(
- metadata: new V1ObjectMeta(name: "dev-validators"),
- webhooks: validationWebhooks.ToList()).Initialize();
+ var validatorConfig = new V1ValidatingWebhookConfiguration(
+ metadata: new V1ObjectMeta(name: "dev-validators"),
+ webhooks: validationWebhooks.ToList()).Initialize();
- if (validatorConfig.Webhooks.Any())
- {
- await Client.SaveAsync(validatorConfig);
- }
+ if (validatorConfig.Webhooks.Any())
+ {
+ await Client.SaveAsync(validatorConfig);
}
}
}
diff --git a/src/KubeOps.Operator/Events/KubeOpsEventPublisherFactory.cs b/src/KubeOps.Operator/Events/KubeOpsEventPublisherFactory.cs
index 20ef9278..0988ae1d 100644
--- a/src/KubeOps.Operator/Events/KubeOpsEventPublisherFactory.cs
+++ b/src/KubeOps.Operator/Events/KubeOpsEventPublisherFactory.cs
@@ -22,7 +22,7 @@ public EventPublisher Create() => async (entity, reason, message, type, token) =
{
var @namespace = entity.Namespace() ?? "default";
logger.LogTrace(
- "Encoding event name with: {resourceName}.{resourceNamespace}.{reason}.{message}.{type}.",
+ "Encoding event name with: {ResourceName}.{ResourceNamespace}.{Reason}.{Message}.{Type}.",
entity.Name(),
@namespace,
reason,
@@ -35,7 +35,7 @@ public EventPublisher Create() => async (entity, reason, message, type, token) =
SHA512.HashData(
Encoding.UTF8.GetBytes(eventName)));
- logger.LogTrace("""Search or create event with name "{name}".""", encodedEventName);
+ logger.LogTrace("""Search or create event with name "{Name}".""", encodedEventName);
Corev1Event? @event;
try
@@ -46,7 +46,7 @@ public EventPublisher Create() => async (entity, reason, message, type, token) =
{
logger.LogError(
e,
- """Could not receive event with name "{name}" on entity "{kind}/{name}".""",
+ """Could not receive event with name "{EventName}" on entity "{Kind}/{Name}".""",
eventName,
entity.Kind,
entity.Name());
@@ -80,7 +80,7 @@ public EventPublisher Create() => async (entity, reason, message, type, token) =
@event.Count++;
@event.LastTimestamp = DateTime.UtcNow;
logger.LogTrace(
- "Save event with new count {count} and last timestamp {timestamp}",
+ "Save event with new count {Count} and last timestamp {Timestamp}",
@event.Count,
@event.LastTimestamp);
@@ -88,7 +88,7 @@ public EventPublisher Create() => async (entity, reason, message, type, token) =
{
await client.SaveAsync(@event, token);
logger.LogInformation(
- """Created or updated event with name "{name}" to new count {count} on entity "{kind}/{name}".""",
+ """Created or updated event with name "{EventName}" to new count {Count} on entity "{Kind}/{Name}".""",
eventName,
@event.Count,
entity.Kind,
@@ -98,7 +98,7 @@ public EventPublisher Create() => async (entity, reason, message, type, token) =
{
logger.LogError(
e,
- """Could not publish event with name "{name}" on entity "{kind}/{name}".""",
+ """Could not publish event with name "{EventName}" on entity "{Kind}/{Name}".""",
eventName,
entity.Kind,
entity.Name());
diff --git a/src/KubeOps.Operator/Finalizer/KubeOpsEventFinalizerAttacherFactory.cs b/src/KubeOps.Operator/Finalizer/KubeOpsEventFinalizerAttacherFactory.cs
index a79d070d..7946536d 100644
--- a/src/KubeOps.Operator/Finalizer/KubeOpsEventFinalizerAttacherFactory.cs
+++ b/src/KubeOps.Operator/Finalizer/KubeOpsEventFinalizerAttacherFactory.cs
@@ -19,7 +19,7 @@ public EntityFinalizerAttacher Create
{
logger.LogTrace(
- """Try to add finalizer "{finalizer}" on entity "{kind}/{name}".""",
+ """Try to add finalizer "{Finalizer}" on entity "{Kind}/{Name}".""",
identifier,
entity.Kind,
entity.Name());
@@ -30,7 +30,7 @@ public EntityFinalizerAttacher Create elector.RunAsync(_cts.Token), CancellationToken.None);
+ _ = Task.Run(() => elector.RunUntilLeadershipLostAsync(_cts.Token), CancellationToken.None);
return Task.CompletedTask;
}
diff --git a/src/KubeOps.Operator/Queue/EntityRequeueBackgroundService.cs b/src/KubeOps.Operator/Queue/EntityRequeueBackgroundService.cs
index 6b95f568..71a7e0cc 100644
--- a/src/KubeOps.Operator/Queue/EntityRequeueBackgroundService.cs
+++ b/src/KubeOps.Operator/Queue/EntityRequeueBackgroundService.cs
@@ -113,13 +113,13 @@ private async Task WatchAsync(CancellationToken cancellationToken)
private async Task ReconcileSingleAsync(TEntity queued, CancellationToken cancellationToken)
{
- logger.LogTrace("""Execute requested requeued reconciliation for "{name}".""", queued.Name());
+ logger.LogTrace("""Execute requested requeued reconciliation for "{Name}".""", queued.Name());
if (await client.GetAsync(queued.Name(), queued.Namespace(), cancellationToken) is not
{ } entity)
{
logger.LogWarning(
- """Requeued entity "{name}" was not found. Skipping reconciliation.""", queued.Name());
+ """Requeued entity "{Name}" was not found. Skipping reconciliation.""", queued.Name());
return;
}
diff --git a/src/KubeOps.Operator/Queue/KubeOpsEntityRequeueFactory.cs b/src/KubeOps.Operator/Queue/KubeOpsEntityRequeueFactory.cs
index 31000164..59a832f6 100644
--- a/src/KubeOps.Operator/Queue/KubeOpsEntityRequeueFactory.cs
+++ b/src/KubeOps.Operator/Queue/KubeOpsEntityRequeueFactory.cs
@@ -19,7 +19,7 @@ public EntityRequeue Create()
var queue = services.GetRequiredService>();
logger?.LogTrace(
- """Requeue entity "{kind}/{name}" in {milliseconds}ms.""",
+ """Requeue entity "{Kind}/{Name}" in {Milliseconds}ms.""",
entity.Kind,
entity.Name(),
timeSpan.TotalMilliseconds);
diff --git a/src/KubeOps.Operator/Watcher/ResourceWatcher{TEntity}.cs b/src/KubeOps.Operator/Watcher/ResourceWatcher{TEntity}.cs
index 1566e83d..58ba7b1a 100644
--- a/src/KubeOps.Operator/Watcher/ResourceWatcher{TEntity}.cs
+++ b/src/KubeOps.Operator/Watcher/ResourceWatcher{TEntity}.cs
@@ -161,7 +161,7 @@ private async Task WatchClientEventsAsync(CancellationToken stoppingToken)
{
if (e.Status.Code == (int)HttpStatusCode.Gone)
{
- logger.LogDebug("Watch restarting due to 410 HTTP Gone");
+ logger.LogDebug(e, "Watch restarting due to 410 HTTP Gone");
break;
}
@@ -213,7 +213,7 @@ private async Task OnEventAsync(WatchEventType type, TEntity? entity, Cancellati
if (entity.Generation() <= cachedGeneration)
{
logger.LogDebug(
- """Entity "{kind}/{name}" modification did not modify generation. Skip event.""",
+ """Entity "{Kind}/{Name}" modification did not modify generation. Skip event.""",
entity.Kind,
entity.Name());
return;
@@ -234,7 +234,7 @@ private async Task OnEventAsync(WatchEventType type, TEntity? entity, Cancellati
break;
default:
logger.LogWarning(
- """Received unsupported event "{eventType}" for "{kind}/{name}".""",
+ """Received unsupported event "{EventType}" for "{Kind}/{Name}".""",
type,
entity?.Kind,
entity.Name());
@@ -250,7 +250,7 @@ private async Task OnWatchErrorAsync(Exception e)
e.InnerException is JsonException &&
e.InnerException.Message.Contains("The input does not contain any JSON tokens"):
logger.LogDebug(
- """The watcher received an empty response for resource "{resource}".""",
+ """The watcher received an empty response for resource "{Resource}".""",
typeof(TEntity));
return;
@@ -258,19 +258,19 @@ e.InnerException is JsonException &&
e.InnerException is EndOfStreamException &&
e.InnerException.Message.Contains("Attempted to read past the end of the stream."):
logger.LogDebug(
- """The watcher received a known error from the watched resource "{resource}". This indicates that there are no instances of this resource.""",
+ """The watcher received a known error from the watched resource "{Resource}". This indicates that there are no instances of this resource.""",
typeof(TEntity));
return;
}
- logger.LogError(e, """There was an error while watching the resource "{resource}".""", typeof(TEntity));
+ logger.LogError(e, """There was an error while watching the resource "{Resource}".""", typeof(TEntity));
_watcherReconnectRetries++;
var delay = TimeSpan
.FromSeconds(Math.Pow(2, Math.Clamp(_watcherReconnectRetries, 0, 5)))
.Add(TimeSpan.FromMilliseconds(new Random().Next(0, 1000)));
logger.LogWarning(
- "There were {retries} errors / retries in the watcher. Wait {seconds}s before next attempt to connect.",
+ "There were {Retries} errors / retries in the watcher. Wait {Seconds}s before next attempt to connect.",
_watcherReconnectRetries,
delay.TotalSeconds);
await Task.Delay(delay);
@@ -301,7 +301,7 @@ private async Task ReconcileFinalizersSequentialAsync(TEntity entity, Cancellati
{ } finalizer)
{
logger.LogDebug(
- """Entity "{kind}/{name}" is finalizing but this operator has no registered finalizers for the identifier {finalizerIdentifier}.""",
+ """Entity "{Kind}/{Name}" is finalizing but this operator has no registered finalizers for the identifier {FinalizerIdentifier}.""",
entity.Kind,
entity.Name(),
identifier);
@@ -312,7 +312,7 @@ private async Task ReconcileFinalizersSequentialAsync(TEntity entity, Cancellati
entity.RemoveFinalizer(identifier);
await client.UpdateAsync(entity, cancellationToken);
logger.LogInformation(
- """Entity "{kind}/{name}" finalized with "{finalizer}".""",
+ """Entity "{Kind}/{Name}" finalized with "{Finalizer}".""",
entity.Kind,
entity.Name(),
identifier);
diff --git a/test/KubeOps.Cli.Test/KubeOps.Cli.Test.csproj b/test/KubeOps.Cli.Test/KubeOps.Cli.Test.csproj
index ce1ef0e3..0af15b70 100644
--- a/test/KubeOps.Cli.Test/KubeOps.Cli.Test.csproj
+++ b/test/KubeOps.Cli.Test/KubeOps.Cli.Test.csproj
@@ -6,11 +6,11 @@
-
-
-
-
-
+
+
+
+
+
diff --git a/test/KubeOps.Generator.Test/KubeOps.Generator.Test.csproj b/test/KubeOps.Generator.Test/KubeOps.Generator.Test.csproj
index ca277cff..06faf69e 100644
--- a/test/KubeOps.Generator.Test/KubeOps.Generator.Test.csproj
+++ b/test/KubeOps.Generator.Test/KubeOps.Generator.Test.csproj
@@ -3,10 +3,10 @@
-
-
-
+
+
+
-
+
diff --git a/test/KubeOps.Operator.Test/KubeOps.Operator.Test.csproj b/test/KubeOps.Operator.Test/KubeOps.Operator.Test.csproj
index e1176b36..974c6f29 100644
--- a/test/KubeOps.Operator.Test/KubeOps.Operator.Test.csproj
+++ b/test/KubeOps.Operator.Test/KubeOps.Operator.Test.csproj
@@ -1,12 +1,12 @@
-
+
-
-
-
-
-
+
+
+
+
+
diff --git a/test/KubeOps.Operator.Web.Test/Builder/OperatorBuilderExtensions.Test.cs b/test/KubeOps.Operator.Web.Test/Builder/OperatorBuilderExtensions.Test.cs
index a72dc3d7..9dc495df 100644
--- a/test/KubeOps.Operator.Web.Test/Builder/OperatorBuilderExtensions.Test.cs
+++ b/test/KubeOps.Operator.Web.Test/Builder/OperatorBuilderExtensions.Test.cs
@@ -1,6 +1,4 @@
-using System.Xml.Linq;
-
-using FluentAssertions;
+using FluentAssertions;
using KubeOps.Abstractions.Builder;
using KubeOps.Abstractions.Certificates;
diff --git a/test/KubeOps.Operator.Web.Test/Certificates/CertificateGenerator.Test.cs b/test/KubeOps.Operator.Web.Test/Certificates/CertificateGenerator.Test.cs
index 6d3cb78b..61fce27f 100644
--- a/test/KubeOps.Operator.Web.Test/Certificates/CertificateGenerator.Test.cs
+++ b/test/KubeOps.Operator.Web.Test/Certificates/CertificateGenerator.Test.cs
@@ -2,42 +2,43 @@
using FluentAssertions;
-namespace KubeOps.Operator.Web.Test.Certificates
+using KubeOps.Operator.Web.Certificates;
+
+namespace KubeOps.Operator.Web.Test.Certificates;
+
+public class CertificateGeneratorTest : IDisposable
{
- public class CertificateGeneratorTest : IDisposable
+ private readonly CertificateGenerator _certificateGenerator = new(Environment.MachineName);
+
+ [Fact]
+ public void Root_Should_Be_Valid()
+ {
+ var (certificate, key) = _certificateGenerator.Root;
+
+ certificate.Should().NotBeNull();
+ DateTime.Parse(certificate.GetEffectiveDateString()).Should().BeOnOrBefore(DateTime.UtcNow);
+ certificate.Extensions.Any(e => e is X509BasicConstraintsExtension basic && basic.CertificateAuthority).Should().BeTrue();
+ certificate.HasPrivateKey.Should().BeTrue();
+
+ key.Should().NotBeNull();
+ }
+
+ [Fact]
+ public void Server_Should_Be_Valid()
+ {
+ var (certificate, key) = _certificateGenerator.Server;
+
+ certificate.Should().NotBeNull();
+ DateTime.Parse(certificate.GetEffectiveDateString()).Should().BeOnOrBefore(DateTime.UtcNow);
+ certificate.Extensions.Any(e => e is X509BasicConstraintsExtension basic && basic.CertificateAuthority).Should().BeFalse();
+ certificate.HasPrivateKey.Should().BeFalse();
+
+ key.Should().NotBeNull();
+ }
+
+ public void Dispose()
{
- private readonly CertificateGenerator _certificateGenerator = new(Environment.MachineName);
-
- [Fact]
- public void Root_Should_Be_Valid()
- {
- var (certificate, key) = _certificateGenerator.Root;
-
- certificate.Should().NotBeNull();
- DateTime.Parse(certificate.GetEffectiveDateString()).Should().BeOnOrBefore(DateTime.UtcNow);
- certificate.Extensions.Any(e => e is X509BasicConstraintsExtension basic && basic.CertificateAuthority).Should().BeTrue();
- certificate.HasPrivateKey.Should().BeTrue();
-
- key.Should().NotBeNull();
- }
-
- [Fact]
- public void Server_Should_Be_Valid()
- {
- var (certificate, key) = _certificateGenerator.Server;
-
- certificate.Should().NotBeNull();
- DateTime.Parse(certificate.GetEffectiveDateString()).Should().BeOnOrBefore(DateTime.UtcNow);
- certificate.Extensions.Any(e => e is X509BasicConstraintsExtension basic && basic.CertificateAuthority).Should().BeFalse();
- certificate.HasPrivateKey.Should().BeFalse();
-
- key.Should().NotBeNull();
- }
-
- public void Dispose()
- {
- _certificateGenerator.Dispose();
- GC.SuppressFinalize(this);
- }
+ _certificateGenerator.Dispose();
+ GC.SuppressFinalize(this);
}
}
diff --git a/test/KubeOps.Operator.Web.Test/KubeOps.Operator.Web.Test.csproj b/test/KubeOps.Operator.Web.Test/KubeOps.Operator.Web.Test.csproj
index 6ec920f9..2b8bc919 100644
--- a/test/KubeOps.Operator.Web.Test/KubeOps.Operator.Web.Test.csproj
+++ b/test/KubeOps.Operator.Web.Test/KubeOps.Operator.Web.Test.csproj
@@ -1,13 +1,13 @@
-
+
-
-
-
-
-
+
+
+
+
+
@@ -16,4 +16,8 @@
+
+
+ CS0618
+
diff --git a/test/KubeOps.Transpiler.Test/KubeOps.Transpiler.Test.csproj b/test/KubeOps.Transpiler.Test/KubeOps.Transpiler.Test.csproj
index e3e7eaf0..238e6337 100644
--- a/test/KubeOps.Transpiler.Test/KubeOps.Transpiler.Test.csproj
+++ b/test/KubeOps.Transpiler.Test/KubeOps.Transpiler.Test.csproj
@@ -1,13 +1,13 @@
-
+
-
-
-
-
-
+
+
+
+
+