From 395109feabbf9eb99709d69fd215bb59006311c4 Mon Sep 17 00:00:00 2001 From: Burak TUNGUT Date: Mon, 21 Aug 2023 04:28:40 +0300 Subject: [PATCH] Release/1.7.0 (#4) * We finally catch the changes on Azure KeyVault * Azure, polly and serilog nuget upgraded * KubernetesClient and Dockerfile is modified * 1.7.0 --- Dockerfile | 4 +- README.md | 2 +- helm/Chart.yaml | 4 +- helm/templates/deployment.yaml | 4 + helm/values.yaml | 9 +- manifests/03-deployment.yaml | 5 +- src/.dockerignore | 285 ++++++++++++++++++ src/AppConfiguration.cs | 1 + src/Domain/JobRuner.cs | 7 +- src/Domain/Jobs/AzureKeyVaultJob.cs | 8 +- src/Domain/ServicePrincipalSecretContainer.cs | 2 +- src/Global.cs | 2 +- src/Handlers/AzureKeyVaultHandler.cs | 77 ++--- src/Operator.csproj | 16 +- src/OperatorFramework/CRDWatcher.cs | 4 +- src/Program.cs | 5 +- 16 files changed, 376 insertions(+), 59 deletions(-) create mode 100644 src/.dockerignore diff --git a/Dockerfile b/Dockerfile index 31c69f0..38309fc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,11 @@ -FROM mcr.microsoft.com/dotnet/sdk:6.0-alpine AS sdk +FROM mcr.microsoft.com/dotnet/sdk:7.0-alpine AS sdk WORKDIR /app COPY . . RUN dotnet restore RUN dotnet publish -c Release -o /app/out -FROM mcr.microsoft.com/dotnet/runtime:6.0-alpine AS runtime +FROM mcr.microsoft.com/dotnet/runtime:7.0-alpine AS runtime WORKDIR /app COPY --from=sdk /app/out . diff --git a/README.md b/README.md index a585804..21ddaa9 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/azure-keyvault-secret-operator)](https://artifacthub.io/packages/search?repo=azure-keyvault-secret-operator) -[![Release](https://img.shields.io/github/v/release/btungut/azure-keyvault-secret-operator?include_prereleases&style=plastic)](https://github.com/btungut/azure-keyvault-secret-operator/releases/tag/0.0.6) +[![Release](https://img.shields.io/github/v/release/btungut/azure-keyvault-secret-operator?include_prereleases&style=plastic)](https://github.com/btungut/azure-keyvault-secret-operator/releases/tag/1.7.0) [![LICENSE](https://img.shields.io/github/license/btungut/azure-keyvault-secret-operator?style=plastic)](https://github.com/btungut/azure-keyvault-secret-operator/blob/master/LICENSE) # Azure KeyVault Secret Operator for Kubernetes diff --git a/helm/Chart.yaml b/helm/Chart.yaml index 31c46f8..1a8ec37 100644 --- a/helm/Chart.yaml +++ b/helm/Chart.yaml @@ -2,8 +2,8 @@ apiVersion: v2 name: kubernetes-azure-keyvault-secret-operator description: Kubernetes Azure KeyVault Secret Operator type: application -version: 0.0.6 -appVersion: "0.0.6" +version: 1.7.0 +appVersion: "1.7.0" sources: - https://github.com/btungut/kubernetes-azure-keyvault-secret-operator home: https://github.com/btungut/kubernetes-azure-keyvault-secret-operator diff --git a/helm/templates/deployment.yaml b/helm/templates/deployment.yaml index d6ec928..1c80842 100644 --- a/helm/templates/deployment.yaml +++ b/helm/templates/deployment.yaml @@ -34,6 +34,10 @@ spec: value: {{ .Values.configs.enableJsonLogging | quote }} - name: reconciliationFrequency value: {{ .Values.configs.reconciliationFrequency | quote }} + {{- if and (hasKey .Values.configs "forceUpdateFrequency") (.Values.configs.forceUpdateFrequency) }} + - name: forceUpdateFrequency + value: {{ .Values.configs.forceUpdateFrequency | quote }} + {{- end }} resources: {{- toYaml .Values.resources | nindent 12 }} {{- with .Values.nodeSelector }} diff --git a/helm/values.yaml b/helm/values.yaml index 2c469d3..ebbe01d 100644 --- a/helm/values.yaml +++ b/helm/values.yaml @@ -3,16 +3,23 @@ fullnameOverride: "" image: repository: btungut/azure-keyvault-secret-operator - tag: 0.0.6 + tag: 1.7.0 configs: # valid values: Verbose, Debug, Information, Warning, Error, Fatal (default : Information) logLevel: "Information" + # valid values: true, false as string enableJsonLogging: "false" + # timespan hh:mm:ss reconciliationFrequency: "00:00:30" + # timespan (nullable) hh:mm:ss + # It forces to update all secrets in specified timespan. + forceUpdateFrequency: "00:03:00" + + rbac: enabled: true diff --git a/manifests/03-deployment.yaml b/manifests/03-deployment.yaml index afa47a5..000620b 100644 --- a/manifests/03-deployment.yaml +++ b/manifests/03-deployment.yaml @@ -19,7 +19,7 @@ spec: restartPolicy: Always containers: - name: operator - image: btungut/azure-keyvault-secret-operator:0.0.6 + image: btungut/azure-keyvault-secret-operator:1.7.0 env: # valid values: Verbose, Debug, Information, Warning, Error, Fatal (default : Information) - name: logLevel @@ -32,6 +32,9 @@ spec: # hh:mm:ss - name: reconciliationFrequency value: "00:00:30" + + - name: forceUpdateFrequency + value: "00:03:00" resources: limits: memory: "128Mi" diff --git a/src/.dockerignore b/src/.dockerignore new file mode 100644 index 0000000..45e467e --- /dev/null +++ b/src/.dockerignore @@ -0,0 +1,285 @@ + +# Created by https://www.gitignore.io/api/csharp + +### Csharp ### +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ +Properties/launchSettings.json + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/ +tools/Cake.CoreCLR +.vscode +tools +.dotnet +Dockerfile + +# .env file contains default environment variables for docker +.env +.git/ \ No newline at end of file diff --git a/src/AppConfiguration.cs b/src/AppConfiguration.cs index d2af29f..aeff425 100644 --- a/src/AppConfiguration.cs +++ b/src/AppConfiguration.cs @@ -9,5 +9,6 @@ internal class AppConfiguration public int WorkerCount { get; private set; } = 1; public TimeSpan ReconciliationFrequency { get; set; } = TimeSpan.FromSeconds(30); + public TimeSpan? ForceUpdateFrequency { get; set; } = null; } } diff --git a/src/Domain/JobRuner.cs b/src/Domain/JobRuner.cs index bba1e7e..b27d68b 100644 --- a/src/Domain/JobRuner.cs +++ b/src/Domain/JobRuner.cs @@ -2,6 +2,7 @@ { internal class JobRuner { + public DateTime? LastExecutedAt { get; private set; } private ILogger _logger = LoggerFactory.GetLogger>(); private BlockingCollection _queue; private Task[] _tasks; @@ -30,7 +31,11 @@ public JobRuner(Job job) } } - public void Enqueue(TJobParameter jobParameter) => _queue.Add(jobParameter); + public void Enqueue(TJobParameter jobParameter) + { + LastExecutedAt = DateTime.UtcNow; + _queue.Add(jobParameter); + } } internal abstract class Job diff --git a/src/Domain/Jobs/AzureKeyVaultJob.cs b/src/Domain/Jobs/AzureKeyVaultJob.cs index 913b9d7..ab6fc15 100644 --- a/src/Domain/Jobs/AzureKeyVaultJob.cs +++ b/src/Domain/Jobs/AzureKeyVaultJob.cs @@ -71,7 +71,7 @@ private async Task ProcessManagedSecret(AzureKeyVault owner, AzureKeyVault.Azure return; } - var clusterNamespaces = (await _client.ListNamespaceAsync()).Items.Select(x => x.Metadata.Name).ToArray(); + var clusterNamespaces = (await _client.CoreV1.ListNamespaceAsync()).Items.Select(x => x.Metadata.Name).ToArray(); var managedSecrets = PatternResolver.ResolveManagedSecrets(clusterNamespaces, managedSecretDefinition); _logger.Debug("Total {count} secret will be created for {owner} namespace/name pairings : {pairs}", managedSecrets.Count(), owner.Metadata.Name, managedSecrets); @@ -149,7 +149,7 @@ private async Task> PopulateDataDictionaryAsync(IDict private async Task CreateOrUpdateSecretAsync(AzureKeyVault owner, Resource resource, string secretType, Dictionary data, IDictionary labels) { - var secretGetResult = await _client.InvokeAsync(c => c.ReadNamespacedSecretAsync(resource.Name, resource.Namespace)); + var secretGetResult = await _client.InvokeAsync(c => c.CoreV1.ReadNamespacedSecretAsync(resource.Name, resource.Namespace)); if (secretGetResult.IsSucceeded) { @@ -158,7 +158,7 @@ private async Task CreateOrUpdateSecretAsync(AzureKeyVault owner, Resource resou V1Secret secret = secretGetResult.Data; FillV1Secret(owner, secret, secretType, data, labels); - var replaceResult = await _client.InvokeAsync(c => c.ReplaceNamespacedSecretAsync(secret, resource.Name, resource.Namespace)); + var replaceResult = await _client.InvokeAsync(c => c.CoreV1.ReplaceNamespacedSecretAsync(secret, resource.Name, resource.Namespace)); if (!replaceResult.IsSucceeded) { string requestContent = replaceResult.Exception.GetRequestContentIfPossible(); @@ -181,7 +181,7 @@ private async Task CreateOrUpdateSecretAsync(AzureKeyVault owner, Resource resou }; FillV1Secret(owner, secret, secretType, data, labels); - var createResult = await _client.InvokeAsync(c => c.CreateNamespacedSecretAsync(secret, resource.Namespace)); + var createResult = await _client.InvokeAsync(c => c.CoreV1.CreateNamespacedSecretAsync(secret, resource.Namespace)); if (!createResult.IsSucceeded) { string requestContent = createResult.Exception.GetRequestContentIfPossible(); diff --git a/src/Domain/ServicePrincipalSecretContainer.cs b/src/Domain/ServicePrincipalSecretContainer.cs index f98c634..cd146be 100644 --- a/src/Domain/ServicePrincipalSecretContainer.cs +++ b/src/Domain/ServicePrincipalSecretContainer.cs @@ -26,7 +26,7 @@ public static async Task GetAsync(string @namespace, string name) return result; } - var apiResult = await Program.KubernetesClient.InvokeAsync(c => c.ReadNamespacedSecretAsync(resource.Name, resource.Namespace)); + var apiResult = await Program.KubernetesClient.InvokeAsync(c => c.CoreV1.ReadNamespacedSecretAsync(resource.Name, resource.Namespace)); if (apiResult.Status == System.Net.HttpStatusCode.NotFound) { _logger.Error("Secret is not found at {resource}", resource); diff --git a/src/Global.cs b/src/Global.cs index cfe361b..f43ee0d 100644 --- a/src/Global.cs +++ b/src/Global.cs @@ -11,7 +11,7 @@ global using OperatorFramework.Contracts; global using Serilog; global using Polly; -global using Microsoft.Rest; +global using k8s.Autorest; global using System; global using System.Collections.Concurrent; global using System.Collections.Generic; diff --git a/src/Handlers/AzureKeyVaultHandler.cs b/src/Handlers/AzureKeyVaultHandler.cs index 15c0f55..24336e0 100644 --- a/src/Handlers/AzureKeyVaultHandler.cs +++ b/src/Handlers/AzureKeyVaultHandler.cs @@ -56,7 +56,7 @@ public async Task OnReconciliation(IKubernetes client) _logger.Information("OnReconciliation is starting"); - var clusterNamespaces = (await client.ListNamespaceAsync()).Items.Select(x => x.Metadata.Name).ToArray(); + var clusterNamespaces = (await client.CoreV1.ListNamespaceAsync()).Items.Select(x => x.Metadata.Name).ToArray(); await ReconcileManagedSecrets(bag, client, clusterNamespaces); await ReconcileDanglingSecrets(bag, client, clusterNamespaces); @@ -72,7 +72,7 @@ private async Task ReconcileDanglingSecrets(KeyValuePair new Resource(s.Namespace(), s.Name()))); } @@ -89,7 +89,7 @@ private async Task ReconcileDanglingSecrets(KeyValuePair c.DeleteNamespacedSecretAsync(secret.Name, secret.Namespace)); + var apiResult = await client.InvokeAsync(c => c.CoreV1.DeleteNamespacedSecretAsync(secret.Name, secret.Namespace)); if (apiResult.IsSucceeded) { succeededCount++; @@ -113,13 +113,13 @@ private async Task ReconcileDanglingSecrets(KeyValuePair x.Metadata.Name).ToArray(); + var clusterNamespaces = (await client.CoreV1.ListNamespaceAsync()).Items.Select(x => x.Metadata.Name).ToArray(); var managedSecrets = PatternResolver.ResolveManagedSecrets(clusterNamespaces, crd.Spec.ManagedSecrets); _logger.Information("Secret finalization is starting for {@resource}", managedSecrets); foreach (var secret in managedSecrets) { - var apiResult = await client.InvokeAsync(c => c.DeleteNamespacedSecretAsync(secret.Name, secret.Namespace)); + var apiResult = await client.InvokeAsync(c => c.CoreV1.DeleteNamespacedSecretAsync(secret.Name, secret.Namespace)); if (apiResult.IsSucceeded) { @@ -141,40 +141,49 @@ private async Task ReconcileManagedSecrets(KeyValuePair var secretsNeedsToBeValid = PatternResolver.ResolveManagedSecrets(clusterNamespaces, crd.Value.Spec.ManagedSecrets); foreach (var secret in secretsNeedsToBeValid) { - var apiResult = await client.InvokeAsync(c => c.ReadNamespacedSecretAsync(secret.Name, secret.Namespace)); - - var secretSyncVersion = apiResult.Data?.GetAnnotation(Constants.SecretSyncVersionAnnotation); - if (apiResult.IsSucceeded && secretSyncVersion != null && Convert.ToInt32(secretSyncVersion) == crd.Value.Spec.SyncVersion) - { - _logger.Information("OnReconciliation : {resource} syncVersion:{version} is exist and syncVersions are same, no need to take action.", secret, secretSyncVersion); - continue; - } - - //Secret is found but syncVersion is null (maybe manually deleted) - if (apiResult.IsSucceeded && secretSyncVersion == null) - { - _logger.Warning("OnReconciliation : syncVersion of Secret is null, it will be processed."); - } - //Secret is found but syncVersion is changed - else if (apiResult.IsSucceeded && secretSyncVersion != null) - { - _logger.Warning( - "OnReconciliation : Expected {crdVersion} and actual {secretVersion} is not same, it will be processed.", - crd.Value.Spec.SyncVersion, secretSyncVersion ?? "(null)"); - } - //Secret is not found and we still responsible to manage it. - else if (apiResult.Status == System.Net.HttpStatusCode.NotFound && Context.IsExist(crd.Key)) + var forceUpdateFreq = Program.AppConfiguration.ForceUpdateFrequency; + if (!(forceUpdateFreq.HasValue && _jobRunner.LastExecutedAt.HasValue && (DateTime.UtcNow - _jobRunner.LastExecutedAt) >= forceUpdateFreq)) { - _logger.Warning( - "OnReconciliation : Secret {secretResource} for {crdResource} is not found, it will be processed.", - crd.Key, secret); + var apiResult = await client.InvokeAsync(c => c.CoreV1.ReadNamespacedSecretAsync(secret.Name, secret.Namespace)); + + var secretSyncVersion = apiResult.Data?.GetAnnotation(Constants.SecretSyncVersionAnnotation); + if (apiResult.IsSucceeded && secretSyncVersion != null && Convert.ToInt32(secretSyncVersion) == crd.Value.Spec.SyncVersion) + { + _logger.Information("OnReconciliation : {resource} syncVersion:{version} is exist and syncVersions are same, no need to take action.", secret, secretSyncVersion); + continue; + } + + //Secret is found but syncVersion is null (maybe manually deleted) + if (apiResult.IsSucceeded && secretSyncVersion == null) + { + _logger.Warning("OnReconciliation : syncVersion of Secret is null, it will be processed."); + } + //Secret is found but syncVersion is changed + else if (apiResult.IsSucceeded && secretSyncVersion != null) + { + _logger.Warning( + "OnReconciliation : Expected {crdVersion} and actual {secretVersion} is not same, it will be processed.", + crd.Value.Spec.SyncVersion, secretSyncVersion ?? "(null)"); + } + //Secret is not found and we still responsible to manage it. + else if (apiResult.Status == System.Net.HttpStatusCode.NotFound && Context.IsExist(crd.Key)) + { + _logger.Warning( + "OnReconciliation : Secret {secretResource} for {crdResource} is not found, it will be processed.", + crd.Key, secret); + } + else + { + _logger.Error( + apiResult.Exception, "OnReconciliation : Unexpected case {secretResource} {crdResource}", + crd.Key, secret); + } } else { - _logger.Error( - apiResult.Exception, "OnReconciliation : Unexpected case {secretResource} {crdResource}", - crd.Key, secret); + _logger.Information("ForceUpdateFrequency ({freq} seconds) is passed. Secrets will be processed...", forceUpdateFreq.Value.TotalSeconds); } + ServicePrincipalSecretContainer.Flush(); _jobRunner.Enqueue(crd.Value); diff --git a/src/Operator.csproj b/src/Operator.csproj index ebc5368..371228e 100644 --- a/src/Operator.csproj +++ b/src/Operator.csproj @@ -2,20 +2,20 @@ Exe - net6.0 + net7.0 enable - - - - - - + + + + + + - + diff --git a/src/OperatorFramework/CRDWatcher.cs b/src/OperatorFramework/CRDWatcher.cs index a64a4d4..f2e82b3 100644 --- a/src/OperatorFramework/CRDWatcher.cs +++ b/src/OperatorFramework/CRDWatcher.cs @@ -71,7 +71,7 @@ private async Task StartWatcherAsync() { _logger.Debug("Watcher object is initiating..."); - var response = await Client.ListClusterCustomObjectWithHttpMessagesAsync(_configuration.Group, _configuration.Version, _configuration.Plural, watch: true); + var response = await Client.CustomObjects.ListClusterCustomObjectWithHttpMessagesAsync(_configuration.Group, _configuration.Version, _configuration.Plural, watch: true); _watcher = response.Watch( onEvent: async (_eventType, _crd) => await OnChange(_eventType, _crd).ConfigureAwait(false), onClosed: async () => await OnClosed().ConfigureAwait(false)); @@ -160,7 +160,7 @@ private static async Task ValidateAsync(IKubernetes client, CRDConfiguration con string crdName = $"{configuration.Plural}.{configuration.Group}"; _logger.Information("Checking CRD {name}", crdName); - var apiResult = await client.InvokeAsync(c => c.ReadCustomResourceDefinitionAsync(crdName)); + var apiResult = await client.InvokeAsync(c => c.ApiextensionsV1.ReadCustomResourceDefinitionAsync(crdName)); if (!apiResult.IsSucceeded) { _logger.Error(apiResult.Exception, "CRD is not found!"); diff --git a/src/Program.cs b/src/Program.cs index 049eafb..eed802a 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -55,8 +55,8 @@ private static void ConfigureKubernetesClient() config = KubernetesClientConfiguration.BuildConfigFromConfigFile(); } + config.HttpClientTimeout = TimeSpan.FromSeconds(10); KubernetesClient = new Kubernetes(config); - KubernetesClient.HttpClient.Timeout = TimeSpan.FromSeconds(10); } private static void Bootstrap() @@ -74,6 +74,9 @@ private static void Bootstrap() //Validation if (AppConfiguration.ReconciliationFrequency < TimeSpan.FromSeconds(10)) throw new ArgumentOutOfRangeException(nameof(AppConfiguration.ReconciliationFrequency), "ReconciliationFrequency couldn't be less than 10 seconds."); + + if (AppConfiguration.ForceUpdateFrequency.HasValue && AppConfiguration.ForceUpdateFrequency.Value < TimeSpan.FromSeconds(30)) + throw new ArgumentOutOfRangeException(nameof(AppConfiguration.ForceUpdateFrequency), "ForceUpdateFrequency couldn't be less than 30 seconds."); } private static void ConfigureLogging()