V1PersistentVolumeClaim ExtractPvc(Mount mount, IDictionary <string, string> labels) { string name = KubeUtils.SanitizeK8sValue(mount.Source); bool readOnly = mount.ReadOnly; var persistentVolumeClaimSpec = new V1PersistentVolumeClaimSpec() { // What happens if the PV access mode is not compatible with the access we're requesting? // Deployment will be created and will be in a failed state. The user will see this as // module running == false. AccessModes = new List <string> { readOnly ? "ReadOnlyMany" : "ReadWriteMany" }, Resources = new V1ResourceRequirements() { Requests = new Dictionary <string, ResourceQuantity>() { { "storage", new ResourceQuantity($"{this.persistentVolumeClaimSizeMb}Mi") } } }, }; // prefer persistent volume name to storage class name, if both are set. if (this.persistentVolumeName.HasValue) { this.persistentVolumeName.ForEach(volumeName => persistentVolumeClaimSpec.VolumeName = volumeName); } else if (this.storageClassName.HasValue) { this.storageClassName.ForEach(storageClass => persistentVolumeClaimSpec.StorageClassName = storageClass); } return(new V1PersistentVolumeClaim(metadata: new V1ObjectMeta(name: name, labels: labels), spec: persistentVolumeClaimSpec)); }
public EdgeDeploymentCommand( string deviceNamespace, ResourceName resourceName, IKubernetes client, IEnumerable <IModule> modules, IRuntimeInfo runtimeInfo, ICombinedConfigProvider <CombinedKubernetesConfig> configProvider) { this.deviceNamespace = KubeUtils.SanitizeK8sValue(Preconditions.CheckNonWhiteSpace(deviceNamespace, nameof(deviceNamespace))); this.resourceName = Preconditions.CheckNotNull(resourceName, nameof(resourceName)); this.client = Preconditions.CheckNotNull(client, nameof(client)); this.modules = Preconditions.CheckNotNull(modules, nameof(modules)).ToList(); this.runtimeInfo = Preconditions.CheckNotNull(runtimeInfo, nameof(runtimeInfo)); this.configProvider = Preconditions.CheckNotNull(configProvider, nameof(configProvider)); this.id = new Lazy <string>(() => this.modules.Aggregate(string.Empty, (prev, module) => module.Name + prev)); this.serializerSettings = new JsonSerializerSettings { ContractResolver = new OverrideJsonIgnoreOfBaseClassContractResolver( new Dictionary <Type, string[]> { [typeof(KubernetesModule)] = new[] { nameof(KubernetesModule.Name) } }) { // Environment variable (env) property JSON casing should be left alone NamingStrategy = new CamelCaseNamingStrategy { ProcessDictionaryKeys = false } } }; }
public async Task <Plan> PlanAsync( ModuleSet desired, ModuleSet current, IRuntimeInfo runtimeInfo, IImmutableDictionary <string, IModuleIdentity> moduleIdentities) { Events.LogDesired(desired); Events.LogCurrent(current); Events.LogIdentities(moduleIdentities); // Check that module names sanitize and remain unique. var groupedModules = desired.Modules.ToLookup(pair => KubeUtils.SanitizeK8sValue(pair.Key)); if (groupedModules.Any(c => c.Count() > 1)) { string nameList = groupedModules .Where(c => c.Count() > 1) .SelectMany(g => g, (pairs, pair) => pair.Key) .Join(","); throw new InvalidIdentityException($"Deployment will cause a name collision in Kubernetes namespace, modules: [{nameList}]"); } // TODO: improve this so it is generic for all potential module types. if (!desired.Modules.Values.All(p => p is IModule <DockerConfig>)) { throw new InvalidModuleException($"Kubernetes deployment currently only handles type={typeof(DockerConfig).FullName}"); } // This is a workaround for K8s Public Preview Refresh // TODO: remove this workaround when merging to the main release desired = new ModuleSet(desired.Modules.Remove(Constants.EdgeAgentModuleName)); current = new ModuleSet(current.Modules.Remove(Constants.EdgeAgentModuleName)); Diff moduleDifference = desired.Diff(current); Plan plan; if (!moduleDifference.IsEmpty) { // The "Plan" here is very simple - if we have any change, publish all desired modules to a EdgeDeployment CRD. // The CRD allows us to give the customer a Kubernetes-centric way to see the deployment // and the status of that deployment through the "edgedeployments" API. var crdCommand = new EdgeDeploymentCommand(this.deviceNamespace, this.resourceName, this.client, desired.Modules.Values, runtimeInfo, this.configProvider); var planCommand = await this.commandFactory.WrapAsync(crdCommand); var planList = new List <ICommand> { planCommand }; Events.PlanCreated(planList); plan = new Plan(planList); } else { plan = Plan.Empty; } return(plan); }
public async Task <Plan> PlanAsync( ModuleSet desired, ModuleSet current, IRuntimeInfo runtimeInfo, IImmutableDictionary <string, IModuleIdentity> moduleIdentities) { Events.LogDesired(desired); Events.LogCurrent(current); Events.LogIdentities(moduleIdentities); // Check that module names sanitize and remain unique. var groupedModules = desired.Modules.GroupBy(pair => KubeUtils.SanitizeK8sValue(pair.Key)).ToArray(); if (groupedModules.Any(c => c.Count() > 1)) { string nameList = groupedModules.Where(c => c.Count() > 1).SelectMany(g => g, (pairs, pair) => pair.Key).Join(","); throw new InvalidIdentityException($"Deployment will cause a name collision in Kubernetes namespace, modules: [{nameList}]"); } // TODO: improve this so it is generic for all potential module types. if (!desired.Modules.Values.All(p => p is IModule <DockerConfig>)) { throw new InvalidModuleException($"Kubernetes deployment currently only handles type={typeof(T).FullName}"); } Diff moduleDifference = desired.Diff(current); Plan plan; if (!moduleDifference.IsEmpty) { // The "Plan" here is very simple - if we have any change, publish all desired modules to a CRD. // The CRD allows us to give the customer a Kubernetes-centric way to see the deployment // and the status of that deployment through the "edgedeployments" API. var k8sModules = desired.Modules.Select(m => new KubernetesModule <DockerConfig>(m.Value as IModule <DockerConfig>)); var crdCommand = new KubernetesCrdCommand <CombinedDockerConfig>(this.deviceNamespace, this.iotHubHostname, this.deviceId, this.client, k8sModules.ToArray(), Option.Some(runtimeInfo), this.combinedConfigProvider as ICombinedConfigProvider <CombinedDockerConfig>); var planCommand = await this.commandFactory.WrapAsync(crdCommand); var planList = new List <ICommand> { planCommand }; Events.PlanCreated(planList); plan = new Plan(planList); } else { plan = Plan.Empty; } return(plan); }
V1PersistentVolumeClaim ExtractPvc(KubernetesModule module, Mount mount, IDictionary <string, string> labels) { string volumeName = KubeUtils.SanitizeK8sValue(mount.Source); string pvcName = KubernetesModule.PvcName(module, mount); bool readOnly = mount.ReadOnly; var persistentVolumeClaimSpec = new V1PersistentVolumeClaimSpec() { // What happens if the PV access mode is not compatible with the access we're requesting? // Deployment will be created and will be in a failed state. The user will see this as // module running == false. AccessModes = new List <string> { readOnly ? "ReadOnlyMany" : "ReadWriteMany" }, Resources = new V1ResourceRequirements() { Requests = new Dictionary <string, ResourceQuantity>() { { "storage", new ResourceQuantity($"{this.persistentVolumeClaimSizeMb}Mi") } } }, }; if (this.persistentVolumeName.HasValue) { string pvName = this.persistentVolumeName.OrDefault(); if (pvName != volumeName) { throw new InvalidModuleException(string.Format("The mount name {0} has to be the same as the PV name {1}", volumeName, pvName)); } persistentVolumeClaimSpec.VolumeName = volumeName; } if (this.storageClassName.HasValue) { persistentVolumeClaimSpec.StorageClassName = this.storageClassName.OrDefault(); } var pvcMeta = new V1ObjectMeta( name: pvcName, labels: labels, ownerReferences: module.Owner.ToOwnerReferences()); return(new V1PersistentVolumeClaim(metadata: pvcMeta, spec: persistentVolumeClaimSpec)); }
public EdgeDeploymentCommand( string deviceNamespace, ResourceName resourceName, IKubernetes client, IEnumerable <IModule> desiredmodules, ModuleSet currentmodules, IRuntimeInfo runtimeInfo, ICombinedConfigProvider <CombinedKubernetesConfig> configProvider) { this.deviceNamespace = KubeUtils.SanitizeK8sValue(Preconditions.CheckNonWhiteSpace(deviceNamespace, nameof(deviceNamespace))); this.resourceName = Preconditions.CheckNotNull(resourceName, nameof(resourceName)); this.client = Preconditions.CheckNotNull(client, nameof(client)); this.modules = Preconditions.CheckNotNull(desiredmodules, nameof(desiredmodules)).ToList(); this.currentmodules = Preconditions.CheckNotNull(currentmodules, nameof(currentmodules)); this.runtimeInfo = Preconditions.CheckNotNull(runtimeInfo, nameof(runtimeInfo)); this.configProvider = Preconditions.CheckNotNull(configProvider, nameof(configProvider)); this.id = new Lazy <string>(() => this.modules.Aggregate(string.Empty, (prev, module) => module.Name + prev)); this.serializerSettings = EdgeDeploymentSerialization.SerializerSettings; }
public KubernetesCrdCommand(string deviceNamespace, string iotHubHostname, string deviceId, IKubernetes client, KubernetesModule <DockerConfig>[] modules, Option <IRuntimeInfo> runtimeInfo, ICombinedConfigProvider <T> combinedConfigProvider) { this.deviceNamespace = KubeUtils.SanitizeK8sValue(Preconditions.CheckNonWhiteSpace(deviceNamespace, nameof(deviceNamespace))); this.iotHubHostname = KubeUtils.SanitizeK8sValue(Preconditions.CheckNonWhiteSpace(iotHubHostname, nameof(iotHubHostname))); this.deviceId = KubeUtils.SanitizeK8sValue(Preconditions.CheckNonWhiteSpace(deviceId, nameof(deviceId))); this.client = Preconditions.CheckNotNull(client, nameof(client)); this.modules = Preconditions.CheckNotNull(modules, nameof(modules)); this.runtimeInfo = Preconditions.CheckNotNull(runtimeInfo, nameof(runtimeInfo)); this.combinedConfigProvider = Preconditions.CheckNotNull(combinedConfigProvider, nameof(combinedConfigProvider)); this.id = new Lazy <string>(() => this.modules.Aggregate(string.Empty, (prev, module) => module.Name + prev)); var deserializerTypesMap = new Dictionary <Type, IDictionary <string, Type> > { [typeof(IModule)] = new Dictionary <string, Type> { ["docker"] = typeof(CombinedDockerConfig) } }; this.deploymentSerde = new TypeSpecificSerDe <EdgeDeploymentDefinition <DockerConfig> >(deserializerTypesMap); }
public bool Equals(V1Deployment x, V1Deployment y) { if (ReferenceEquals(x, y)) { return(true); } if (ReferenceEquals(x, null)) { return(false); } if (ReferenceEquals(y, null)) { return(false); } if (x.GetType() != y.GetType()) { return(false); } if (x.Metadata.Name != y.Metadata.Name) { return(false); } // EdgeAgent deployments are equal when they have identical image sections if (x.Metadata.Name == KubeUtils.SanitizeK8sValue(CoreConstants.EdgeAgentModuleName)) { return(V1DeploymentEx.ImageEquals(x, y)); } // compares by creation string string xCreationString = GetCreationString(x); string yCreationString = GetCreationString(y); return(xCreationString == yCreationString); }
public async Task <Plan> PlanAsync( ModuleSet desired, ModuleSet current, IRuntimeInfo runtimeInfo, IImmutableDictionary <string, IModuleIdentity> moduleIdentities) { Events.LogDesired(desired); // We receive current ModuleSet from Agent based on what it reports (i.e. pods). // We need to rebuild the current ModuleSet based on deployments (i.e. CRD). Option <EdgeDeploymentDefinition> activeDeployment = await this.GetCurrentEdgeDeploymentDefinitionAsync(); ModuleSet currentModules = activeDeployment.Match( a => ModuleSet.Create(a.Spec.ToArray()), () => ModuleSet.Empty); Events.LogCurrent(currentModules); // Check that module names sanitize and remain unique. var groupedModules = desired.Modules.ToLookup(pair => KubeUtils.SanitizeK8sValue(pair.Key)); if (groupedModules.Any(c => c.Count() > 1)) { string nameList = groupedModules .Where(c => c.Count() > 1) .SelectMany(g => g, (pairs, pair) => pair.Key) .Join(","); throw new InvalidIdentityException($"Deployment will cause a name collision in Kubernetes namespace, modules: [{nameList}]"); } // TODO: improve this so it is generic for all potential module types. if (!desired.Modules.Values.All(p => p is IModule <DockerConfig>)) { throw new InvalidModuleException($"Kubernetes deployment currently only handles type={typeof(DockerConfig).FullName}"); } Diff moduleDifference = desired.Diff(currentModules); Plan plan; if (!moduleDifference.IsEmpty) { // The "Plan" here is very simple - if we have any change, publish all desired modules to a EdgeDeployment CRD. var crdCommand = new EdgeDeploymentCommand(this.resourceName, this.deviceSelector, this.deviceNamespace, this.client, desired.Modules.Values, activeDeployment, runtimeInfo, this.configProvider, this.moduleOwner); var planCommand = await this.commandFactory.WrapAsync(crdCommand); var planList = new List <ICommand> { planCommand }; Events.PlanCreated(planList); plan = new Plan(planList); } else { plan = Plan.Empty; } return(plan); }
protected override void Load(ContainerBuilder builder) { // IKubernetesClient builder.Register( c => { if (this.enableServiceCallTracing) { // enable tracing of k8s requests made by the client var loggerFactory = c.Resolve <ILoggerFactory>(); ILogger logger = loggerFactory.CreateLogger(typeof(Kubernetes)); ServiceClientTracing.IsEnabled = true; ServiceClientTracing.AddTracingInterceptor(new DebugTracer(logger)); } // load the k8s config from KUBECONFIG or $HOME/.kube/config or in-cluster if its available KubernetesClientConfiguration kubeConfig = Option.Maybe(Environment.GetEnvironmentVariable("KUBECONFIG")) .Else(() => Option.Maybe(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".kube", "config"))) .Filter(File.Exists) .Map(path => KubernetesClientConfiguration.BuildConfigFromConfigFile(path)) .GetOrElse(KubernetesClientConfiguration.InClusterConfig); return(new Kubernetes(kubeConfig)); }) .As <IKubernetes>() .SingleInstance(); // IModuleClientProvider builder.Register( c => new ModuleClientProvider( c.Resolve <ISdkModuleClientProvider>(), this.upstreamProtocol, this.proxy, this.productInfo.OrDefault(), this.closeOnIdleTimeout, this.idleTimeout)) .As <IModuleClientProvider>() .SingleInstance(); // IModuleManager builder.Register(c => new ModuleManagementHttpClient(this.managementUri, this.apiVersion, Core.Constants.EdgeletClientApiVersion)) .As <IModuleManager>() .As <IIdentityManager>() .SingleInstance(); // IModuleIdentityLifecycleManager var identityBuilder = new ModuleIdentityProviderServiceBuilder(this.resourceName.Hostname, this.resourceName.DeviceId, this.edgeDeviceHostName); builder.Register(c => new KubernetesModuleIdentityLifecycleManager(c.Resolve <IIdentityManager>(), identityBuilder, this.workloadUri)) .As <IModuleIdentityLifecycleManager>() .SingleInstance(); // CombinedKubernetesConfigProvider builder.Register( c => { bool enableKubernetesExtensions = this.experimentalFeatures.Enabled && this.experimentalFeatures.EnableExtensions; return(new CombinedKubernetesConfigProvider(this.dockerAuthConfig, this.workloadUri, this.managementUri, enableKubernetesExtensions)); }) .As <ICombinedConfigProvider <CombinedKubernetesConfig> >() .SingleInstance(); // ICommandFactory builder.Register( c => { var loggerFactory = c.Resolve <ILoggerFactory>(); var kubernetesCommandFactory = new KubernetesCommandFactory(); ICommandFactory factory = new LoggingCommandFactory(kubernetesCommandFactory, loggerFactory); return(Task.FromResult(factory)); }) .As <Task <ICommandFactory> >() .SingleInstance(); // IPlanner builder.Register( async c => { var configProvider = c.Resolve <ICombinedConfigProvider <CombinedKubernetesConfig> >(); ICommandFactory commandFactory = await c.Resolve <Task <ICommandFactory> >(); IPlanner planner = new KubernetesPlanner(this.deviceNamespace, this.resourceName, c.Resolve <IKubernetes>(), commandFactory, configProvider); return(planner); }) .As <Task <IPlanner> >() .SingleInstance(); // KubernetesRuntimeInfoProvider builder.Register(c => new KubernetesRuntimeInfoProvider(this.deviceNamespace, c.Resolve <IKubernetes>(), c.Resolve <IModuleManager>())) .As <IRuntimeInfoProvider>() .As <IRuntimeInfoSource>() .SingleInstance(); // KubernetesDeploymentProvider builder.Register( c => new KubernetesDeploymentMapper( this.deviceNamespace, this.edgeDeviceHostName, this.proxyImage, this.proxyConfigPath, this.proxyConfigVolumeName, this.proxyConfigMapName, this.proxyTrustBundlePath, this.proxyTrustBundleVolumeName, this.proxyTrustBundleConfigMapName, this.persistentVolumeName, this.storageClassName, this.apiVersion, this.workloadUri, this.managementUri)) .As <IKubernetesDeploymentMapper>(); // KubernetesServiceMapper builder.Register(c => new KubernetesServiceMapper(this.defaultMapServiceType)) .As <IKubernetesServiceMapper>(); // KubernetesPvcMapper builder.Register(c => new KubernetesPvcMapper(this.persistentVolumeName, this.storageClassName, this.persistentVolumeClaimSizeMb)) .As <IKubernetesPvcMapper>(); // KubernetesServiceAccountProvider builder.Register(c => new KubernetesServiceAccountMapper()) .As <IKubernetesServiceAccountMapper>(); // EdgeDeploymentController builder.Register( c => { var deploymentSelector = $"{Constants.K8sEdgeDeviceLabel}={KubeUtils.SanitizeK8sValue(this.resourceName.DeviceId)},{Constants.K8sEdgeHubNameLabel}={KubeUtils.SanitizeK8sValue(this.resourceName.Hostname)}"; IEdgeDeploymentController watchOperator = new EdgeDeploymentController( this.resourceName, deploymentSelector, this.deviceNamespace, c.Resolve <IKubernetes>(), c.Resolve <IModuleIdentityLifecycleManager>(), c.Resolve <IKubernetesServiceMapper>(), c.Resolve <IKubernetesDeploymentMapper>(), c.Resolve <IKubernetesPvcMapper>(), c.Resolve <IKubernetesServiceAccountMapper>()); return(watchOperator); }) .As <IEdgeDeploymentController>() .SingleInstance(); // IEdgeDeploymentOperator builder.Register( c => { IEdgeDeploymentOperator watchOperator = new EdgeDeploymentOperator( this.resourceName, this.deviceNamespace, c.Resolve <IKubernetes>(), c.Resolve <IEdgeDeploymentController>()); return(watchOperator); }) .As <IEdgeDeploymentOperator>() .SingleInstance(); // IKubernetesEnvironmentOperator builder.Register( c => { IKubernetesEnvironmentOperator watchOperator = new KubernetesEnvironmentOperator( this.deviceNamespace, c.Resolve <IRuntimeInfoSource>(), c.Resolve <IKubernetes>()); return(watchOperator); }) .As <IKubernetesEnvironmentOperator>() .SingleInstance(); // Task<IEnvironmentProvider> builder.Register( async c => { var moduleStateStore = c.Resolve <IEntityStore <string, ModuleState> >(); var restartPolicyManager = c.Resolve <IRestartPolicyManager>(); IRuntimeInfoProvider runtimeInfoProvider = c.Resolve <IRuntimeInfoProvider>(); IEnvironmentProvider dockerEnvironmentProvider = await DockerEnvironmentProvider.CreateAsync(runtimeInfoProvider, moduleStateStore, restartPolicyManager); return(dockerEnvironmentProvider); }) .As <Task <IEnvironmentProvider> >() .SingleInstance(); }
public KubernetesModule( string iotHubHostname, string deviceId, string edgeDeviceHostName, string proxyImage, Option <string> proxyImagePullSecretName, string proxyConfigPath, string proxyConfigVolumeName, string proxyConfigMapName, string proxyTrustBundlePath, string proxyTrustBundleVolumeName, string proxyTrustBundleConfigMapName, string apiVersion, string deviceNamespace, Uri managementUri, Uri workloadUri, IEnumerable <global::Docker.DotNet.Models.AuthConfig> dockerAuthConfig, Option <UpstreamProtocol> upstreamProtocol, Option <string> productInfo, PortMapServiceType defaultMapServiceType, bool enableServiceCallTracing, string persistentVolumeName, string storageClassName, Option <uint> persistentVolumeClaimSizeMb, Option <IWebProxy> proxy, bool closeOnIdleTimeout, TimeSpan idleTimeout, KubernetesExperimentalFeatures experimentalFeatures, KubernetesModuleOwner moduleOwner, bool runAsNonRoot) { this.resourceName = new ResourceName(iotHubHostname, deviceId); this.edgeDeviceHostName = Preconditions.CheckNonWhiteSpace(edgeDeviceHostName, nameof(edgeDeviceHostName)); this.proxyImage = Preconditions.CheckNonWhiteSpace(proxyImage, nameof(proxyImage)); this.proxyImagePullSecretName = proxyImagePullSecretName; this.proxyConfigPath = Preconditions.CheckNonWhiteSpace(proxyConfigPath, nameof(proxyConfigPath)); this.proxyConfigVolumeName = Preconditions.CheckNonWhiteSpace(proxyConfigVolumeName, nameof(proxyConfigVolumeName)); this.proxyConfigMapName = Preconditions.CheckNonWhiteSpace(proxyConfigMapName, nameof(proxyConfigMapName)); this.proxyTrustBundlePath = Preconditions.CheckNonWhiteSpace(proxyTrustBundlePath, nameof(proxyTrustBundlePath)); this.proxyTrustBundleVolumeName = Preconditions.CheckNonWhiteSpace(proxyTrustBundleVolumeName, nameof(proxyTrustBundleVolumeName)); this.proxyTrustBundleConfigMapName = Preconditions.CheckNonWhiteSpace(proxyTrustBundleConfigMapName, nameof(proxyTrustBundleConfigMapName)); this.apiVersion = Preconditions.CheckNonWhiteSpace(apiVersion, nameof(apiVersion)); this.deviceSelector = $"{Constants.K8sEdgeDeviceLabel}={KubeUtils.SanitizeK8sValue(this.resourceName.DeviceId)},{Constants.K8sEdgeHubNameLabel}={KubeUtils.SanitizeK8sValue(this.resourceName.Hostname)}"; this.deviceNamespace = Preconditions.CheckNonWhiteSpace(deviceNamespace, nameof(deviceNamespace)); this.managementUri = Preconditions.CheckNotNull(managementUri, nameof(managementUri)); this.workloadUri = Preconditions.CheckNotNull(workloadUri, nameof(workloadUri)); this.dockerAuthConfig = Preconditions.CheckNotNull(dockerAuthConfig, nameof(dockerAuthConfig)); this.upstreamProtocol = Preconditions.CheckNotNull(upstreamProtocol, nameof(upstreamProtocol)); this.productInfo = productInfo; this.defaultMapServiceType = defaultMapServiceType; this.enableServiceCallTracing = enableServiceCallTracing; this.persistentVolumeName = persistentVolumeName; this.storageClassName = storageClassName; this.persistentVolumeClaimSizeMb = persistentVolumeClaimSizeMb; this.proxy = proxy; this.closeOnIdleTimeout = closeOnIdleTimeout; this.idleTimeout = idleTimeout; this.experimentalFeatures = experimentalFeatures; this.moduleOwner = moduleOwner; this.runAsNonRoot = runAsNonRoot; }
public void SanitizeK8sValueFailTest(string raw) => Assert.Throws <InvalidKubernetesNameException>(() => KubeUtils.SanitizeK8sValue(raw));
public void SanitizeK8sValueTest(string expected, string raw) => Assert.Equal(expected, KubeUtils.SanitizeK8sValue(raw));
private string DeploymentName(string moduleId) => KubeUtils.SanitizeK8sValue(moduleId);
public async Task ManageFileShareSecretAsync(V1Secret secret) { byte[] accountKeyData; byte[] accountNameData; if (!secret.Data.TryGetValue(AccountKey, out accountKeyData)) { Console.WriteLine($"Secret {secret.Metadata.Name} doesn't have [{AccountKey}] Data"); return; } if (!secret.Data.TryGetValue(AccountName, out accountNameData)) { Console.WriteLine($"Secret {secret.Metadata.Name} doesn't have [{AccountName}] Data"); return; } var pvLabels = new Dictionary <string, string> { [Constants.LabelSelectorKey] = Constants.LabelSelectorValue }; var mountOptions = new List <string> { "dir_mode=0777", "file_mode=0777", "uid=1000", "gid=1000", "mfsymlinks", "nobrl" }; V1PersistentVolumeList currentPvs = await k8sClient.ListPersistentVolumeAsync(labelSelector : Constants.LabelSelector); var existingPvSet = new Set <V1PersistentVolume>(currentPvs.Items .Where(pv => pv.Spec?.AzureFile?.SecretName == secret.Metadata.Name) .ToDictionary(pv => pv.Metadata.Name)); var desiredPvs = new ConcurrentDictionary <string, V1PersistentVolume>(); string accountKey = Encoding.UTF8.GetString(accountKeyData); string accountName = Encoding.UTF8.GetString(accountNameData); string connectionString = $"DefaultEndpointsProtocol=https;AccountName={accountName};AccountKey={accountKey};EndpointSuffix=core.windows.net"; // Open a FileShare client with secret. var serviceClient = new ShareServiceClient(connectionString); var shares = serviceClient.GetSharesAsync(ShareTraits.Metadata, ShareStates.None); await foreach (var share in shares) { // Get all file shares from client that match a trait if ((share.Properties?.Metadata != null) && (share.Properties.Metadata.TryGetValue(Constants.LabelSelectorKey, out string labelValue)) && (labelValue == Constants.LabelSelectorValue)) { // Create a PV from secret and ShareItem Console.WriteLine($"ShareItem {share.Name} found!"); string name = KubeUtils.SanitizeK8sValue($"{accountName}-{share.Name}"); var metadata = new V1ObjectMeta(name: name, labels: pvLabels); var accessModes = new List <string> { AccessMode }; var azurefile = new V1AzureFilePersistentVolumeSource(secret.Metadata.Name, share.Name, readOnlyProperty: false, secret.Metadata.NamespaceProperty); var capacity = new Dictionary <string, ResourceQuantity> { ["storage"] = new ResourceQuantity($"{share.Properties.QuotaInGB}Gi") }; var spec = new V1PersistentVolumeSpec( accessModes: accessModes, azureFile: azurefile, capacity: capacity, storageClassName: StorageClassName, mountOptions: mountOptions); var pv = new V1PersistentVolume(metadata: metadata, spec: spec); if (!desiredPvs.TryAdd(name, pv)) { Console.WriteLine($"Duplicate share name {name}"); } } } var desiredPvSet = new Set <V1PersistentVolume>(desiredPvs); var diff = desiredPvSet.Diff(existingPvSet, PvComparer); await this.ManagePvs(diff); }