using System; using System.Net.Http; using System.Threading; using System.Threading.Tasks; using k8s.Models; using k8s.Util.Common.Generic.Options; using Microsoft.Rest; using Microsoft.Rest.Serialization; namespace k8s.Util.Common.Generic { /// /// /// The Generic kubernetes api provides a unified client interface for not only the non-core-group /// built-in resources from kubernetes but also the custom-resources models meet the following /// requirements: /// /// 1. there's a `V1ObjectMeta` field in the model along with its getter/setter. 2. there's a /// `V1ListMeta` field in the list model along with its getter/setter. - supports Json /// serialization/deserialization. 3. the generic kubernetes api covers all the basic operations over /// the custom resources including {get, list, watch, create, update, patch, delete}. /// /// - For kubernetes-defined failures, the server will return a {@link V1Status} with 4xx/5xx /// code. The status object will be nested in {@link KubernetesApiResponse#getStatus()} - For the /// other unknown reason (including network, JVM..), throws an unchecked exception. /// public class GenericKubernetesApi { private readonly string _apiGroup; private readonly string _apiVersion; private readonly string _resourcePlural; private readonly IKubernetes _client; /// /// Initializes a new instance of the class. /// /// the api group"> /// the api version"> /// the resource plural, e.g. "jobs""> /// optional client"> public GenericKubernetesApi(string apiGroup = default, string apiVersion = default, string resourcePlural = default, IKubernetes apiClient = default) { _apiGroup = apiGroup ?? throw new ArgumentNullException(nameof(apiGroup)); _apiVersion = apiVersion ?? throw new ArgumentNullException(nameof(apiVersion)); _resourcePlural = resourcePlural ?? throw new ArgumentNullException(nameof(resourcePlural)); _client = apiClient ?? new Kubernetes(KubernetesClientConfiguration.BuildDefaultConfig()); } public TimeSpan ClientTimeout => _client.HttpClient.Timeout; public void SetClientTimeout(TimeSpan value) { _client.HttpClient.Timeout = value; } /// /// Get kubernetes object. /// /// the object type /// the object name /// the token /// The object public Task GetAsync(string name, CancellationToken cancellationToken = default) where T : class, IKubernetesObject { return GetAsync(name, new GetOptions(), cancellationToken); } /// /// Get kubernetes object under the namespaceProperty. /// /// the object type /// the namespaceProperty /// the name /// the token /// the kubernetes object public Task GetAsync(string namespaceProperty, string name, CancellationToken cancellationToken = default) where T : class, IKubernetesObject { return GetAsync(namespaceProperty, name, new GetOptions(), cancellationToken); } /// /// List kubernetes object cluster-scoped. /// /// the object type /// the token /// the kubernetes object public Task ListAsync(CancellationToken cancellationToken = default) where T : class, IKubernetesObject { return ListAsync(new ListOptions(), cancellationToken); } /// /// List kubernetes object under the namespaceProperty. /// /// the object type /// the namespace /// the token /// the kubernetes object public Task ListAsync(string namespaceProperty, CancellationToken cancellationToken = default) where T : class, IKubernetesObject { return ListAsync(namespaceProperty, new ListOptions(), cancellationToken); } /// /// Create kubernetes object, if the namespaceProperty in the object is present, it will send a /// namespaceProperty-scoped requests, vice versa. /// /// the object type /// the object /// the token /// the kubernetes object public Task CreateAsync(T obj, CancellationToken cancellationToken = default) where T : class, IKubernetesObject { return CreateAsync(obj, new CreateOptions(), cancellationToken); } /// /// Create kubernetes object, if the namespaceProperty in the object is present, it will send a /// namespaceProperty-scoped requests, vice versa. /// /// the object /// the token /// the object type /// the kubernetes object public Task UpdateAsync(T obj, CancellationToken cancellationToken = default) where T : class, IKubernetesObject { return UpdateAsync(obj, new UpdateOptions(), cancellationToken); } /// /// Patch kubernetes object. /// /// the name /// the string patch content /// the token /// the object type /// the kubernetes object public Task PatchAsync(string name, object patch, CancellationToken cancellationToken = default) where T : class, IKubernetesObject { return PatchAsync(name, patch, new PatchOptions(), cancellationToken); } /// /// Patch kubernetes object under the namespaceProperty. /// /// the namespaceProperty /// the name /// the string patch content /// the token /// the object type /// the kubernetes object public Task PatchAsync(string namespaceProperty, string name, object patch, CancellationToken cancellationToken = default) where T : class, IKubernetesObject { return PatchAsync(namespaceProperty, name, patch, new PatchOptions(), cancellationToken); } /// /// Delete kubernetes object. /// /// the name /// the token /// the object type /// the kubernetes object public Task DeleteAsync(string name, CancellationToken cancellationToken = default) where T : class, IKubernetesObject { return DeleteAsync(name, new V1DeleteOptions(), cancellationToken); } /// /// Delete kubernetes object under the namespaceProperty. /// /// the namespaceProperty /// the name /// the token /// the object type /// the kubernetes object public Task DeleteAsync(string namespaceProperty, string name, CancellationToken cancellationToken = default) where T : class, IKubernetesObject { return DeleteAsync(namespaceProperty, name, new V1DeleteOptions(), cancellationToken); } /// /// Creates a cluster-scoped Watch on the resource. /// /// action on event /// action on error /// action on closed /// the token /// the object type /// the watchable public Watcher Watch(Action onEvent, Action onError = default, Action onClosed = default, CancellationToken cancellationToken = default) where T : class, IKubernetesObject { return Watch(new ListOptions(), onEvent, onError, onClosed, cancellationToken); } /// /// Creates a namespaceProperty-scoped Watch on the resource. /// /// the object type /// the namespaceProperty /// action on event /// action on error /// action on closed /// the token /// the watchable public Watcher Watch(string namespaceProperty, Action onEvent, Action onError = default, Action onClosed = default, CancellationToken cancellationToken = default) where T : class, IKubernetesObject { return Watch(namespaceProperty, new ListOptions(), onEvent, onError, onClosed, cancellationToken); } // TODO(yue9944882): watch one resource? /// /// Get kubernetes object. /// /// the object type /// the name /// the get options /// the token /// the kubernetes object public async Task GetAsync(string name, GetOptions getOptions, CancellationToken cancellationToken = default) where T : class, IKubernetesObject { if (string.IsNullOrEmpty(name)) { throw new ArgumentNullException(nameof(name)); } var resp = await _client.GetClusterCustomObjectWithHttpMessagesAsync(group: _apiGroup, plural: _resourcePlural, version: _apiVersion, name: name, cancellationToken: cancellationToken) .ConfigureAwait(false); return SafeJsonConvert.DeserializeObject(resp.Body.ToString()); } /// /// Get kubernetes object. /// /// the object type /// the namespaceProperty /// the name /// the get options /// the token /// the kubernetes object public async Task GetAsync(string namespaceProperty, string name, GetOptions getOptions, CancellationToken cancellationToken = default) where T : class, IKubernetesObject { if (string.IsNullOrEmpty(name)) { throw new ArgumentNullException(nameof(name)); } if (string.IsNullOrEmpty(namespaceProperty)) { throw new ArgumentNullException(nameof(namespaceProperty)); } var resp = await _client.GetNamespacedCustomObjectWithHttpMessagesAsync(group: _apiGroup, plural: _resourcePlural, version: _apiVersion, name: name, namespaceParameter: namespaceProperty, cancellationToken: cancellationToken).ConfigureAwait(false); return SafeJsonConvert.DeserializeObject(resp.Body.ToString()); } /// /// List kubernetes object. /// /// the object type /// the list options /// the token /// the kubernetes object public async Task ListAsync(ListOptions listOptions, CancellationToken cancellationToken = default) where T : class, IKubernetesObject { if (listOptions == null) { throw new ArgumentNullException(nameof(listOptions)); } var resp = await _client.ListClusterCustomObjectWithHttpMessagesAsync(group: _apiGroup, plural: _resourcePlural, version: _apiVersion, resourceVersion: listOptions.ResourceVersion, continueParameter: listOptions.Continue, fieldSelector: listOptions.FieldSelector, labelSelector: listOptions.LabelSelector, limit: listOptions.Limit, timeoutSeconds: listOptions.TimeoutSeconds, cancellationToken: cancellationToken).ConfigureAwait(false); return SafeJsonConvert.DeserializeObject(resp.Body.ToString()); } /// /// List kubernetes object. /// /// the object type /// the namespaceProperty /// the list options /// the token /// the kubernetes object public async Task ListAsync(string namespaceProperty, ListOptions listOptions, CancellationToken cancellationToken = default) where T : class, IKubernetesObject { if (listOptions == null) { throw new ArgumentNullException(nameof(listOptions)); } if (string.IsNullOrEmpty(namespaceProperty)) { throw new ArgumentNullException(nameof(namespaceProperty)); } var resp = await _client.ListNamespacedCustomObjectWithHttpMessagesAsync(group: _apiGroup, plural: _resourcePlural, version: _apiVersion, resourceVersion: listOptions.ResourceVersion, continueParameter: listOptions.Continue, fieldSelector: listOptions.FieldSelector, labelSelector: listOptions.LabelSelector, limit: listOptions.Limit, timeoutSeconds: listOptions.TimeoutSeconds, namespaceParameter: namespaceProperty, cancellationToken: cancellationToken).ConfigureAwait(false); return SafeJsonConvert.DeserializeObject(resp.Body.ToString()); } /// /// Create kubernetes object. /// /// the object type /// the object /// the create options /// the token /// the kubernetes object public async Task CreateAsync(T obj, CreateOptions createOptions, CancellationToken cancellationToken = default) where T : class, IKubernetesObject { if (obj == null) { throw new ArgumentNullException(nameof(obj)); } if (createOptions == null) { throw new ArgumentNullException(nameof(createOptions)); } V1ObjectMeta objectMeta = obj.Metadata; var isNamespaced = !string.IsNullOrEmpty(objectMeta.NamespaceProperty); if (isNamespaced) { return await CreateAsync(objectMeta.NamespaceProperty, obj, createOptions, cancellationToken).ConfigureAwait(false); } var resp = await _client.CreateClusterCustomObjectWithHttpMessagesAsync(body: obj, group: _apiGroup, plural: _resourcePlural, version: _apiVersion, dryRun: createOptions.DryRun, fieldManager: createOptions.FieldManager, cancellationToken: cancellationToken).ConfigureAwait(false); return SafeJsonConvert.DeserializeObject(resp.Body.ToString()); } /// /// Create namespaced kubernetes object. /// /// the object type /// the namespace /// the object /// the create options /// the token /// the kubernetes object public async Task CreateAsync(string namespaceProperty, T obj, CreateOptions createOptions, CancellationToken cancellationToken = default) where T : class, IKubernetesObject { if (obj == null) { throw new ArgumentNullException(nameof(obj)); } if (createOptions == null) { throw new ArgumentNullException(nameof(createOptions)); } var resp = await _client.CreateNamespacedCustomObjectWithHttpMessagesAsync(body: obj, group: _apiGroup, plural: _resourcePlural, version: _apiVersion, namespaceParameter: namespaceProperty, dryRun: createOptions.DryRun, fieldManager: createOptions.FieldManager, cancellationToken: cancellationToken).ConfigureAwait(false); return SafeJsonConvert.DeserializeObject(resp.Body.ToString()); } /// /// Update kubernetes object. /// /// the object type /// the object /// the update options /// the token /// the kubernetes object public async Task UpdateAsync(T obj, UpdateOptions updateOptions, CancellationToken cancellationToken = default) where T : class, IKubernetesObject { if (obj == null) { throw new ArgumentNullException(nameof(obj)); } if (updateOptions == null) { throw new ArgumentNullException(nameof(updateOptions)); } V1ObjectMeta objectMeta = obj.Metadata; var isNamespaced = !string.IsNullOrEmpty(objectMeta.NamespaceProperty); HttpOperationResponse resp; if (isNamespaced) { resp = await _client.ReplaceNamespacedCustomObjectWithHttpMessagesAsync(body: obj, name: objectMeta.Name, group: _apiGroup, plural: _resourcePlural, version: _apiVersion, namespaceParameter: objectMeta.NamespaceProperty, dryRun: updateOptions.DryRun, fieldManager: updateOptions.FieldManager, cancellationToken: cancellationToken) .ConfigureAwait(false); } else { resp = await _client.ReplaceClusterCustomObjectWithHttpMessagesAsync(body: obj, name: objectMeta.Name, group: _apiGroup ?? obj.ApiGroup(), plural: _resourcePlural, version: _apiVersion, dryRun: updateOptions.DryRun, fieldManager: updateOptions.FieldManager, cancellationToken: cancellationToken).ConfigureAwait(false); } return SafeJsonConvert.DeserializeObject(resp.Body.ToString()); } /// /// Create kubernetes object, if the namespaceProperty in the object is present, it will send a /// namespaceProperty-scoped requests, vice versa. /// /// the object type /// the object /// function to extract the status from the object /// the token /// the kubernetes object public Task UpdateStatusAsync(T obj, Func status, CancellationToken cancellationToken = default) where T : class, IKubernetesObject { return UpdateStatusAsync(obj, status, new UpdateOptions(), cancellationToken); } /// /// Update status of kubernetes object. /// /// the object type /// the object /// function to extract the status from the object /// the update options /// the token /// the kubernetes object public async Task UpdateStatusAsync(T obj, Func status, UpdateOptions updateOptions, CancellationToken cancellationToken = default) where T : class, IKubernetesObject { if (obj == null) { throw new ArgumentNullException(nameof(obj)); } if (updateOptions == null) { throw new ArgumentNullException(nameof(updateOptions)); } V1ObjectMeta objectMeta = obj.Metadata; HttpOperationResponse resp; var isNamespaced = !string.IsNullOrEmpty(objectMeta.NamespaceProperty); if (isNamespaced) { resp = await _client.PatchNamespacedCustomObjectStatusWithHttpMessagesAsync(body: obj, group: _apiGroup, version: _apiVersion, namespaceParameter: objectMeta.NamespaceProperty, plural: _resourcePlural, name: objectMeta.Name, dryRun: updateOptions.DryRun, fieldManager: updateOptions.FieldManager, force: updateOptions.Force, cancellationToken: cancellationToken).ConfigureAwait(false); } else { resp = await _client.PatchClusterCustomObjectStatusWithHttpMessagesAsync(body: obj, group: _apiGroup, version: _apiVersion, plural: _resourcePlural, name: objectMeta.Name, dryRun: updateOptions.DryRun, fieldManager: updateOptions.FieldManager, force: updateOptions.Force, cancellationToken: cancellationToken).ConfigureAwait(false); } return SafeJsonConvert.DeserializeObject(resp.Body.ToString()); } /// /// Patch kubernetes object. /// /// the object type /// the name /// the object /// the patch options /// the token /// the kubernetes object public async Task PatchAsync(string name, object obj, PatchOptions patchOptions, CancellationToken cancellationToken = default) where T : class, IKubernetesObject { if (obj == null) { throw new ArgumentNullException(nameof(obj)); } if (patchOptions == null) { throw new ArgumentNullException(nameof(patchOptions)); } if (string.IsNullOrEmpty(name)) { throw new ArgumentNullException(nameof(name)); } var resp = await _client.PatchClusterCustomObjectWithHttpMessagesAsync(body: obj, group: _apiGroup, version: _apiVersion, plural: _resourcePlural, name: name, dryRun: patchOptions.DryRun, fieldManager: patchOptions.FieldManager, force: patchOptions.Force, cancellationToken: cancellationToken).ConfigureAwait(false); return SafeJsonConvert.DeserializeObject(resp.Body.ToString()); } /// /// Patch kubernetes object. /// /// the object type /// the namespaceProperty /// the name /// the object /// the patch options /// the token /// the kubernetes object public async Task PatchAsync(string namespaceProperty, string name, object obj, PatchOptions patchOptions, CancellationToken cancellationToken = default) where T : class, IKubernetesObject { if (string.IsNullOrEmpty(namespaceProperty)) { throw new ArgumentNullException(nameof(namespaceProperty)); } if (string.IsNullOrEmpty(name)) { throw new ArgumentNullException(nameof(name)); } if (obj == null) { throw new ArgumentNullException(nameof(obj)); } if (patchOptions == null) { throw new ArgumentNullException(nameof(patchOptions)); } var resp = await _client.PatchNamespacedCustomObjectWithHttpMessagesAsync(body: obj, group: _apiGroup, version: _apiVersion, namespaceParameter: namespaceProperty, plural: _resourcePlural, name: name, dryRun: patchOptions.DryRun, fieldManager: patchOptions.FieldManager, force: patchOptions.Force, cancellationToken: cancellationToken).ConfigureAwait(false); return SafeJsonConvert.DeserializeObject(resp.Body.ToString()); } /// /// Delete kubernetes object. /// /// the object type /// the name /// the delete options /// the token /// the kubernetes object public async Task DeleteAsync(string name, V1DeleteOptions deleteOptions, CancellationToken cancellationToken = default) where T : class, IKubernetesObject { if (string.IsNullOrEmpty(name)) { throw new ArgumentNullException(nameof(name)); } var resp = await _client.DeleteClusterCustomObjectWithHttpMessagesAsync( group: _apiGroup, version: _apiVersion, plural: _resourcePlural, name: name, body: deleteOptions, cancellationToken: cancellationToken).ConfigureAwait(false); return SafeJsonConvert.DeserializeObject(resp.Body.ToString()); } /// /// Delete kubernetes object. /// /// the object type /// the namespaceProperty /// the name /// the delete options /// the token /// the kubernetes object public async Task DeleteAsync(string namespaceProperty, string name, V1DeleteOptions deleteOptions, CancellationToken cancellationToken = default) where T : class, IKubernetesObject { if (string.IsNullOrEmpty(namespaceProperty)) { throw new ArgumentNullException(nameof(namespaceProperty)); } if (string.IsNullOrEmpty(name)) { throw new ArgumentNullException(nameof(name)); } var resp = await _client.DeleteNamespacedCustomObjectWithHttpMessagesAsync(group: _apiGroup, version: _apiVersion, namespaceParameter: namespaceProperty, plural: _resourcePlural, name: name, body: deleteOptions, cancellationToken: cancellationToken).ConfigureAwait(false); return SafeJsonConvert.DeserializeObject(resp.Body.ToString()); } /// /// Watch watchable. /// /// the list options /// action on event /// action on error /// action on closed /// the token /// the object type /// the watchable public Watcher Watch(ListOptions listOptions, Action onEvent, Action onError = default, Action onClosed = default, CancellationToken cancellationToken = default) where T : class, IKubernetesObject { if (listOptions == null) { throw new ArgumentNullException(nameof(listOptions)); } var resp = _client.ListClusterCustomObjectWithHttpMessagesAsync(group: _apiGroup, version: _apiVersion, plural: _resourcePlural, continueParameter: listOptions.Continue, fieldSelector: listOptions.FieldSelector, labelSelector: listOptions.LabelSelector, limit: listOptions.Limit, resourceVersion: listOptions.ResourceVersion, timeoutSeconds: listOptions.TimeoutSeconds, watch: true, cancellationToken: cancellationToken); return resp.Watch(onEvent, onError, onClosed); } /// /// Watch watchable. /// /// the namespaceProperty /// the list options /// action on event /// action on error /// action on closed /// the token /// the object type /// the watchable public Watcher Watch(string namespaceProperty, ListOptions listOptions, Action onEvent, Action onError = default, Action onClosed = default, CancellationToken cancellationToken = default) where T : class, IKubernetesObject { if (listOptions == null) { throw new ArgumentNullException(nameof(listOptions)); } if (string.IsNullOrEmpty(namespaceProperty)) { throw new ArgumentNullException(nameof(namespaceProperty)); } var resp = _client.ListNamespacedCustomObjectWithHttpMessagesAsync(group: _apiGroup, version: _apiVersion, namespaceParameter: namespaceProperty, plural: _resourcePlural, continueParameter: listOptions.Continue, fieldSelector: listOptions.FieldSelector, labelSelector: listOptions.LabelSelector, limit: listOptions.Limit, resourceVersion: listOptions.ResourceVersion, timeoutSeconds: listOptions.TimeoutSeconds, watch: true, cancellationToken: cancellationToken); return resp.Watch(onEvent, onError, onClosed); } } }