/// <summary> /// Load configuration entries from the ConfigMap. /// </summary> public override void Load() { ConfigMapV1 configMap = _client.ConfigMapsV1().Get(_configMapName, _kubeNamespace).GetAwaiter().GetResult(); if (configMap != null) { string sectionNamePrefix = !String.IsNullOrWhiteSpace(_sectionName) ? _sectionName + ":" : String.Empty; Data = configMap.Data.ToDictionary( entry => sectionNamePrefix + entry.Key.Replace('.', ':'), entry => entry.Value, StringComparer.OrdinalIgnoreCase ); } else { Data = new Dictionary <string, string>(); } if (_watch && _watchSubscription == null) { _watchSubscription = _client.ConfigMapsV1() .Watch(_configMapName, _kubeNamespace) .Subscribe(configMapEvent => { if (configMapEvent.EventType == ResourceEventType.Modified) { OnReload(); } }); } }
public void ConfigMapV1_Data_PreserveKeyCase(string key) { var model = new ConfigMapV1 { Data = { [key] = key } }; JObject rootObject; using (JTokenWriter writer = new JTokenWriter()) { JsonSerializer.Create(KubeResourceClient.SerializerSettings).Serialize(writer, model); writer.Flush(); rootObject = (JObject)writer.Token; } Log.LogInformation("Serialized:\n{JSON:l}", rootObject.ToString(Formatting.Indented) ); JObject data = rootObject.Value <JObject>("data"); Assert.NotNull(data); Assert.Equal(key, data.Value <string>(key) ); }
/// <summary> /// Load data from the specified ConfigMap. /// </summary> /// <param name="configMap"> /// A <see cref="ConfigMapV1"/> representing the ConfigMap's current state, or <c>null</c> if the ConfigMap was not found. /// </param> /// <param name="isReload"> /// Is the ConfigMap is being reloaded? If <c>false</c>, then this is the initial load (and may throw an exception). /// </param> void Load(ConfigMapV1 configMap, bool isReload = false) { if (configMap != null) { Log.LogTrace("Found ConfigMap {ConfigMapName} in namespace {KubeNamespace} (isReload: {isReload}).", _configMapName, _kubeNamespace ?? _client.DefaultNamespace, isReload); string sectionNamePrefix = !String.IsNullOrWhiteSpace(_sectionName) ? _sectionName + ":" : String.Empty; Data = configMap.Data.ToDictionary( entry => sectionNamePrefix + entry.Key.Replace('.', ':'), entry => entry.Value, StringComparer.OrdinalIgnoreCase ); } else { Data = new Dictionary <string, string>(); Log.LogTrace("ConfigMap {ConfigMapName} was not found in namespace {KubeNamespace} (isReload: {isReload}).", _configMapName, _kubeNamespace ?? _client.DefaultNamespace, isReload); if (!isReload && _throwOnNotFound) { throw new KubeClientException($"ConfigMap {_configMapName} was not found in namespace {_kubeNamespace}."); } } }
/// <summary> /// Update the specified ConfigMap. /// </summary> /// <param name="client"> /// The <see cref="ConfigMapV1"/> resource client. /// </param> /// <param name="configMap"> /// A <see cref="ConfigMapV1"/> representing the new state for the ConfigMap. /// </param> /// <param name="cancellationToken"> /// An optional <see cref="CancellationToken"/> that can be used to cancel the operation. /// </param> /// <returns> /// A <see cref="ConfigMapV1"/> representing the updated ConfigMap. /// </returns> /// <remarks> /// Updates all mutable fields (if specified on <paramref name="configMap"/>). /// </remarks> public static Task <ConfigMapV1> Update(this ConfigMapClientV1 client, ConfigMapV1 configMap, CancellationToken cancellationToken = default) { if (client == null) { throw new ArgumentNullException(nameof(client)); } if (String.IsNullOrWhiteSpace(configMap?.Metadata?.Name)) { throw new ArgumentException("Cannot update a ConfigMap if its metadata does not specify a name.", nameof(configMap)); } if (String.IsNullOrWhiteSpace(configMap?.Metadata?.Namespace)) { throw new ArgumentException("Cannot update a ConfigMap if its metadata does not specify a namespace.", nameof(configMap)); } return(client.Update( name: configMap.Metadata.Name, kubeNamespace: configMap.Metadata.Namespace, patchAction: patch => { if (configMap.Metadata.Labels != null) { patch.Replace(patchConfigMap => patchConfigMap.Metadata.Labels, value: configMap.Metadata.Labels ); } if (configMap.Metadata.Annotations != null) { patch.Replace(patchConfigMap => patchConfigMap.Metadata.Annotations, value: configMap.Metadata.Annotations ); } if (configMap.Data != null) { patch.Replace(patchConfigMap => patchConfigMap.Data, value: configMap.Data ); } }, cancellationToken: cancellationToken )); }
/// <summary> /// Request creation of a <see cref="ConfigMapV1"/>. /// </summary> /// <param name="newConfigMap"> /// A <see cref="ConfigMapV1"/> representing the ConfigMap to create. /// </param> /// <param name="cancellationToken"> /// An optional <see cref="CancellationToken"/> that can be used to cancel the request. /// </param> /// <returns> /// A <see cref="ConfigMapV1"/> representing the current state for the newly-created ConfigMap. /// </returns> public async Task <ConfigMapV1> Create(ConfigMapV1 newConfigMap, CancellationToken cancellationToken = default) { if (newConfigMap == null) { throw new ArgumentNullException(nameof(newConfigMap)); } return(await Http .PostAsJsonAsync( Requests.Collection.WithTemplateParameters(new { Namespace = newConfigMap?.Metadata?.Namespace ?? KubeClient.DefaultNamespace }), postBody : newConfigMap, cancellationToken : cancellationToken ) .ReadContentAsAsync <ConfigMapV1, StatusV1>()); }
/// <summary> /// Load configuration entries from the ConfigMap. /// </summary> public override void Load() { Log.LogTrace("Attempting to load ConfigMap {ConfigMapName} in namespace {KubeNamespace}...", _configMapName, _kubeNamespace ?? _client.DefaultNamespace); ConfigMapV1 configMap = _client.ConfigMapsV1().Get(_configMapName, _kubeNamespace).GetAwaiter().GetResult(); Load(configMap); if (_watch && _watchSubscription == null) { Log.LogTrace("Creating watch-event stream for ConfigMap {ConfigMapName} in namespace {KubeNamespace}...", _configMapName, _kubeNamespace ?? _client.DefaultNamespace); _watchSubscription = _client.ConfigMapsV1() .Watch(_configMapName, _kubeNamespace) .Subscribe(OnConfigMapChanged); Log.LogTrace("Watch-event stream created for ConfigMap {ConfigMapName} in namespace {KubeNamespace}.", _configMapName, _kubeNamespace ?? _client.DefaultNamespace); } }
/// <summary> /// Load data from the specified ConfigMap. /// </summary> /// <param name="configMap"> /// A <see cref="ConfigMapV1"/> representing the ConfigMap's current state, or <c>null</c> if the ConfigMap was not found. /// </param> void Load(ConfigMapV1 configMap) { if (configMap != null) { Log.LogTrace("Found ConfigMap {ConfigMapName} in namespace {KubeNamespace}.", _configMapName, _kubeNamespace ?? _client.DefaultNamespace); string sectionNamePrefix = !String.IsNullOrWhiteSpace(_sectionName) ? _sectionName + ":" : String.Empty; Data = configMap.Data.ToDictionary( entry => sectionNamePrefix + entry.Key.Replace('.', ':'), entry => entry.Value, StringComparer.OrdinalIgnoreCase ); } else { Log.LogTrace("ConfigMap {ConfigMapName} was not found in namespace {KubeNamespace}.", _configMapName, _kubeNamespace ?? _client.DefaultNamespace); Data = new Dictionary <string, string>(); } }
/// <summary> /// The main program entry-point. /// </summary> /// <param name="commandLineArguments"> /// The program's command-line arguments. /// </param> /// <returns> /// The program exit-code. /// </returns> static async Task <int> Main(string[] commandLineArguments) { ProgramOptions options = ProgramOptions.Parse(commandLineArguments); if (options == null) { return(ExitCodes.InvalidArguments); } ILoggerFactory loggerFactory = ConfigureLogging(options); try { const string configMapName = "config-from-configmap"; const string configMapNamespace = "default"; KubeClientOptions clientOptions = K8sConfig.Load().ToKubeClientOptions(defaultKubeNamespace: configMapNamespace, loggerFactory: loggerFactory); if (options.Verbose) { clientOptions.LogPayloads = true; } KubeApiClient client = KubeApiClient.Create(clientOptions); Log.Information("Checking for existing ConfigMap..."); ConfigMapV1 configMap = await client.ConfigMapsV1().Get(configMapName, configMapNamespace); if (configMap != null) { Log.Information("Deleting existing ConfigMap..."); await client.ConfigMapsV1().Delete(configMapName); Log.Information("Existing ConfigMap deleted."); } Log.Information("Creating new ConfigMap..."); configMap = await client.ConfigMapsV1().Create(new ConfigMapV1 { Metadata = new ObjectMetaV1 { Name = configMapName, Namespace = configMapNamespace }, Data = { ["Key1"] = "One", ["Key2"] = "Two" } }); Log.Information("New ConfigMap created."); Log.Information("Building configuration..."); IConfiguration configuration = new ConfigurationBuilder() .AddKubeConfigMap(clientOptions, configMapName: "config-from-configmap", reloadOnChange: true ) .Build(); Log.Information("Configuration built."); Log.Information("Got configuration:"); Dump(configuration); Log.Information("Press enter to update ConfigMap..."); Console.ReadLine(); Log.Information("Registering callback for change-notifications..."); ManualResetEvent configurationChanged = new ManualResetEvent(false); // See ConfigurationExtensions class below. IDisposable reloadNotifications = configuration.OnReload(() => { Log.Information("Got changed configuration:"); Dump(configuration); configurationChanged.Set(); }); Log.Information("Change-notification callback registered."); using (configurationChanged) using (reloadNotifications) { Log.Information("Updating ConfigMap..."); configMap.Data["One"] = "1"; configMap.Data["Two"] = "2"; // Replace the entire Data dictionary (to modify only some of the data, you'll need to use an untyped JsonPatchDocument). await client.ConfigMapsV1().Update(configMapName, patch => { patch.Replace(patchConfigMap => patchConfigMap.Data, value: configMap.Data ); }); Log.Information("Updated ConfigMap."); Log.Information("Waiting for configuration change..."); configurationChanged.WaitOne(); Log.Information("Configuration changed."); } return(ExitCodes.Success); } catch (HttpRequestException <StatusV1> kubeError) { Log.Error(kubeError, "Kubernetes API error: {@Status}", kubeError.Response); return(ExitCodes.UnexpectedError); } catch (Exception unexpectedError) { Log.Error(unexpectedError, "Unexpected error."); return(ExitCodes.UnexpectedError); } }
/// <summary> /// The main program entry-point. /// </summary> /// <param name="commandLineArguments"> /// The program's command-line arguments. /// </param> /// <returns> /// The program exit-code. /// </returns> static async Task <int> Main(string[] commandLineArguments) { ProgramOptions options = ProgramOptions.Parse(commandLineArguments); if (options == null) { return(ExitCodes.InvalidArguments); } ILoggerFactory loggerFactory = ConfigureLogging(options); try { const string configMapName = "config-from-configmap"; const string configMapNamespace = "default"; KubeClientOptions clientOptions = Config.Load().ToKubeClientOptions(defaultKubeNamespace: configMapNamespace); if (options.Verbose) { clientOptions.LogPayloads = true; } KubeApiClient client = KubeApiClient.Create(clientOptions, loggerFactory); Log.Information("Checking for existing ConfigMap..."); ConfigMapV1 configMap = await client.ConfigMapsV1().Get(configMapName, configMapNamespace); if (configMap != null) { Log.Information("Deleting existing ConfigMap..."); await client.ConfigMapsV1().Delete(configMapName); Log.Information("Existing ConfigMap deleted."); } Log.Information("Creating new ConfigMap..."); configMap = await client.ConfigMapsV1().Create(new ConfigMapV1 { Metadata = new ObjectMetaV1 { Name = configMapName, Namespace = configMapNamespace }, Data = new Dictionary <string, string> { ["Key1"] = "One", ["Key2"] = "Two" } }); Log.Information("New ConfigMap created."); Log.Information("Building configuration..."); IConfiguration configuration = new ConfigurationBuilder() .AddKubeConfigMap(clientOptions, configMapName: "config-from-configmap", reloadOnChange: true ) .Build(); Log.Information("Configuration built."); configuration.GetReloadToken().RegisterChangeCallback(_ => { Log.Information("Got changed configuration:"); foreach (var item in configuration.AsEnumerable()) { Log.Information("\t'{Key}' = '{Value}'", item.Key, item.Value); } }, state: null); Log.Information("Got configuration:"); foreach (var item in configuration.AsEnumerable()) { Log.Information("\t'{Key}' = '{Value}'", item.Key, item.Value); } Log.Information("Press enter to update ConfigMap..."); Console.ReadLine(); Log.Information("Updating ConfigMap..."); configMap.Data["One"] = "1"; configMap.Data["Two"] = "2"; // Replace the entire Data dictionary (otherwise, use an untyped JsonPatchDocument). await client.ConfigMapsV1().Update(configMapName, patch => { patch.Replace(patchConfigMap => patchConfigMap.Data, value: configMap.Data ); }); Log.Information("Updated ConfigMap."); Log.Information("Waiting for configuration change; press enter to terminate."); Console.ReadLine(); return(ExitCodes.Success); } catch (HttpRequestException <StatusV1> kubeError) { Log.Error(kubeError, "Kubernetes API error: {@Status}", kubeError.Response); return(ExitCodes.UnexpectedError); } catch (Exception unexpectedError) { Log.Error(unexpectedError, "Unexpected error."); return(ExitCodes.UnexpectedError); } }