too much for one commit
This commit is contained in:
@@ -1,29 +1,45 @@
|
||||
using EntKube.Clusters.Features.AdoptCluster;
|
||||
|
||||
namespace EntKube.Clusters.Domain;
|
||||
|
||||
/// <summary>
|
||||
/// A KubernetesCluster represents a registered cluster that EntKube manages.
|
||||
/// It holds the connection details, health state, and metadata needed to
|
||||
/// interact with the cluster's API server. This is the aggregate root for
|
||||
/// all cluster-related operations.
|
||||
/// interact with the cluster's API server. Every cluster belongs to exactly
|
||||
/// one tenant — no cluster can exist without an owning tenant. The kubeconfig
|
||||
/// is stored as the credential material for API access.
|
||||
/// </summary>
|
||||
public class KubernetesCluster
|
||||
{
|
||||
public Guid Id { get; private set; }
|
||||
public Guid TenantId { get; private set; }
|
||||
public Guid EnvironmentId { get; private set; }
|
||||
public string Name { get; private set; } = string.Empty;
|
||||
public string ApiServerUrl { get; private set; } = string.Empty;
|
||||
public string ContextName { get; private set; } = string.Empty;
|
||||
public ClusterStatus Status { get; private set; }
|
||||
public string? KubeConfigSecret { get; private set; }
|
||||
public CloudProvider Provider { get; private set; }
|
||||
public ProviderCredentials? ProviderCredentials { get; private set; }
|
||||
public string KubeConfig { get; private set; } = string.Empty;
|
||||
public DateTimeOffset RegisteredAt { get; private set; }
|
||||
public DateTimeOffset? LastHealthCheckAt { get; private set; }
|
||||
public IReadOnlyList<PrometheusEndpoint> PrometheusEndpoints => prometheusEndpoints.AsReadOnly();
|
||||
public IReadOnlyList<ClusterComponent> Components => components.AsReadOnly();
|
||||
public IReadOnlyList<StorageBucket> StorageBuckets => storageBuckets.AsReadOnly();
|
||||
|
||||
private List<PrometheusEndpoint> prometheusEndpoints = new();
|
||||
private List<ClusterComponent> components = new();
|
||||
private List<StorageBucket> storageBuckets = new();
|
||||
|
||||
private KubernetesCluster() { }
|
||||
|
||||
/// <summary>
|
||||
/// Registers a new cluster in the platform. At this point, we know the cluster
|
||||
/// exists and we have connection details — but we haven't verified connectivity yet.
|
||||
/// Registers a new cluster in the platform. The caller must provide a tenant,
|
||||
/// cluster name, API server URL, kubeconfig, and the environment it belongs to.
|
||||
/// The cluster starts in a Pending state until a health check confirms it's reachable.
|
||||
/// Every cluster must be assigned to an environment — no orphan clusters allowed.
|
||||
/// </summary>
|
||||
public static KubernetesCluster Register(string name, string apiServerUrl, string? kubeConfigSecret)
|
||||
public static KubernetesCluster Register(string name, string apiServerUrl, string kubeConfig, Guid tenantId, string contextName, Guid environmentId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
@@ -35,12 +51,35 @@ public class KubernetesCluster
|
||||
throw new ArgumentException("API server URL is required.", nameof(apiServerUrl));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(kubeConfig))
|
||||
{
|
||||
throw new ArgumentException("KubeConfig is required.", nameof(kubeConfig));
|
||||
}
|
||||
|
||||
if (tenantId == Guid.Empty)
|
||||
{
|
||||
throw new ArgumentException("A tenant must be specified.", nameof(tenantId));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(contextName))
|
||||
{
|
||||
throw new ArgumentException("A context name must be specified.", nameof(contextName));
|
||||
}
|
||||
|
||||
if (environmentId == Guid.Empty)
|
||||
{
|
||||
throw new ArgumentException("An environment must be specified.", nameof(environmentId));
|
||||
}
|
||||
|
||||
return new KubernetesCluster
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
TenantId = tenantId,
|
||||
EnvironmentId = environmentId,
|
||||
Name = name,
|
||||
ApiServerUrl = apiServerUrl,
|
||||
KubeConfigSecret = kubeConfigSecret,
|
||||
ContextName = contextName,
|
||||
KubeConfig = kubeConfig,
|
||||
Status = ClusterStatus.Pending,
|
||||
RegisteredAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
@@ -66,8 +105,167 @@ public class KubernetesCluster
|
||||
Status = ClusterStatus.Unreachable;
|
||||
LastHealthCheckAt = DateTimeOffset.UtcNow;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assigns (or reassigns) this cluster to an environment. A cluster lives
|
||||
/// in exactly one environment at a time — for example dev01 belongs to "dev",
|
||||
/// test01 and test02 belong to "test", prod01 belongs to "prod".
|
||||
/// </summary>
|
||||
public void AssignToEnvironment(Guid environmentId)
|
||||
{
|
||||
if (environmentId == Guid.Empty)
|
||||
{
|
||||
throw new ArgumentException("Environment ID is required.", nameof(environmentId));
|
||||
}
|
||||
|
||||
EnvironmentId = environmentId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces all discovered Prometheus endpoints with a fresh scan result.
|
||||
/// Called after the platform scans the cluster for Prometheus services.
|
||||
/// If multiple instances are found, they are all stored so metrics can
|
||||
/// be aggregated from all of them.
|
||||
/// </summary>
|
||||
public void SetPrometheusEndpoints(List<PrometheusEndpoint> endpoints)
|
||||
{
|
||||
prometheusEndpoints = endpoints ?? new List<PrometheusEndpoint>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces all tracked components with a fresh scan result from the adoption
|
||||
/// check. Each component check result is converted into a ClusterComponent
|
||||
/// that persists the detected state — status, version, namespace, config.
|
||||
/// This is called after every adoption scan so stored state stays current.
|
||||
/// </summary>
|
||||
public void UpdateComponents(List<ComponentCheckResult> checkResults)
|
||||
{
|
||||
components = checkResults
|
||||
.Select(ClusterComponent.FromCheckResult)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merges new configuration values into an already-tracked component.
|
||||
/// Called after a successful configure operation so the persisted config
|
||||
/// reflects what's actually deployed. If the component hasn't been
|
||||
/// tracked yet (no prior scan), this is a no-op.
|
||||
/// </summary>
|
||||
public void UpdateComponentConfiguration(string componentName, Dictionary<string, string> values)
|
||||
{
|
||||
ClusterComponent? component = components.FirstOrDefault(
|
||||
c => c.ComponentName.Equals(componentName, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
component?.MergeConfiguration(values);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks a tracked component as Installed after a successful deployment.
|
||||
/// If the component hasn't been tracked yet, this is a no-op.
|
||||
/// </summary>
|
||||
public void MarkComponentInstalled(string componentName)
|
||||
{
|
||||
ClusterComponent? component = components.FirstOrDefault(
|
||||
c => c.ComponentName.Equals(componentName, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
component?.MarkInstalled();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the version of a tracked component after a successful upgrade.
|
||||
/// If the component hasn't been tracked yet, this is a no-op.
|
||||
/// </summary>
|
||||
public void UpdateComponentVersion(string componentName, string version)
|
||||
{
|
||||
ClusterComponent? component = components.FirstOrDefault(
|
||||
c => c.ComponentName.Equals(componentName, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
component?.UpdateVersion(version);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks a tracked component as NotInstalled after a successful uninstall.
|
||||
/// If the component hasn't been tracked yet, this is a no-op.
|
||||
/// </summary>
|
||||
public void MarkComponentUninstalled(string componentName)
|
||||
{
|
||||
ClusterComponent? component = components.FirstOrDefault(
|
||||
c => c.ComponentName.Equals(componentName, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
component?.MarkUninstalled();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assigns a cloud provider to this cluster. Providers like Cleura offer
|
||||
/// additional APIs (OpenStack, Gardener) that EntKube can leverage for
|
||||
/// provisioning, billing, and resource management. Credentials are required
|
||||
/// for any provider other than None.
|
||||
/// </summary>
|
||||
public void SetProvider(CloudProvider provider, ProviderCredentials? credentials)
|
||||
{
|
||||
if (provider != CloudProvider.None && credentials is null)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"Credentials are required when setting provider to {provider}.",
|
||||
nameof(credentials));
|
||||
}
|
||||
|
||||
Provider = provider;
|
||||
ProviderCredentials = provider == CloudProvider.None ? null : credentials;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a newly created storage bucket on this cluster. The bucket
|
||||
/// has already been created on the S3 backend — this persists the metadata
|
||||
/// so the platform knows about it and components can reference it.
|
||||
/// </summary>
|
||||
public void AddStorageBucket(StorageBucket bucket)
|
||||
{
|
||||
storageBuckets.Add(bucket);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a storage bucket from this cluster's tracked buckets.
|
||||
/// Called after the bucket has been deleted from the S3 backend.
|
||||
/// </summary>
|
||||
public void RemoveStorageBucket(Guid bucketId)
|
||||
{
|
||||
storageBuckets.RemoveAll(b => b.Id == bucketId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The cloud provider hosting a Kubernetes cluster. Determines which additional
|
||||
/// APIs are available for management (OpenStack, Gardener, billing, etc.).
|
||||
/// </summary>
|
||||
public enum CloudProvider
|
||||
{
|
||||
None,
|
||||
Cleura,
|
||||
Aws,
|
||||
Azure,
|
||||
Gcp
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Credentials for authenticating with a cluster's cloud provider APIs.
|
||||
/// Cleura exposes two separate APIs:
|
||||
/// 1. Cleura Cloud REST API (rest.cleura.cloud) — uses Cleura login credentials
|
||||
/// for account management, Gardener K8s cluster ops, billing, domain listing.
|
||||
/// 2. OpenStack API (e.g. sto2.citycloud.com:5000) — uses OpenStack/Keystone
|
||||
/// credentials for infrastructure: compute, networking, storage, load balancing.
|
||||
/// Both credential sets are stored here; either may be null if not configured.
|
||||
/// </summary>
|
||||
public record ProviderCredentials(
|
||||
string Username,
|
||||
string Password,
|
||||
string Region,
|
||||
string? OpenStackAuthUrl = null,
|
||||
string? OpenStackProjectId = null,
|
||||
string? OpenStackUsername = null,
|
||||
string? OpenStackPassword = null,
|
||||
string? OpenStackUserDomainName = null);
|
||||
|
||||
public enum ClusterStatus
|
||||
{
|
||||
Pending,
|
||||
|
||||
Reference in New Issue
Block a user