예제 #1
0
        static Option <V1ContainerStatus> GetContainerByName(string name, V1Pod pod)
        {
            string            containerName = KubeUtils.SanitizeDNSValue(name);
            V1ContainerStatus status        = pod.Status?.ContainerStatuses?
                                              .FirstOrDefault(container => string.Equals(container.Name, containerName, StringComparison.OrdinalIgnoreCase));

            return(Option.Maybe(status));
        }
예제 #2
0
 public void SanitizeDnsValueFailTest(string raw) => Assert.Throws <InvalidKubernetesNameException>(() => KubeUtils.SanitizeDNSValue(raw));
예제 #3
0
 public void SanitizeDnsValueTest(string expected, string raw) => Assert.Equal(expected, KubeUtils.SanitizeDNSValue(raw));
예제 #4
0
        private Option <V1Service> GetServiceFromModule(Dictionary <string, string> labels, KubernetesModule <TConfig> module, IModuleIdentity moduleIdentity)
        {
            var portList = new List <V1ServicePort>();
            Option <Dictionary <string, string> > serviceAnnotations = Option.None <Dictionary <string, string> >();
            bool onlyExposedPorts = true;

            if (module is IModule <AgentDocker.CombinedDockerConfig> moduleWithDockerConfig)
            {
                if (moduleWithDockerConfig.Config.CreateOptions?.Labels != null)
                {
                    // Add annotations from Docker labels. This provides the customer a way to assign annotations to services if they want
                    // to tie backend services to load balancers via an Ingress Controller.
                    var annotations = new Dictionary <string, string>();
                    foreach (KeyValuePair <string, string> label in moduleWithDockerConfig.Config.CreateOptions?.Labels)
                    {
                        annotations.Add(KubeUtils.SanitizeAnnotationKey(label.Key), label.Value);
                    }

                    serviceAnnotations = Option.Some(annotations);
                }

                // Handle ExposedPorts entries
                if (moduleWithDockerConfig.Config?.CreateOptions?.ExposedPorts != null)
                {
                    // Entries in the Exposed Port list just tell Docker that this container wants to listen on that port.
                    // We interpret this as a "ClusterIP" service type listening on that exposed port, backed by this module.
                    // Users of this Module's exposed port should be able to find the service by connecting to "<module name>:<port>"
                    this.GetExposedPorts(moduleWithDockerConfig.Config.CreateOptions.ExposedPorts)
                    .ForEach(
                        exposedList =>
                        exposedList.ForEach((item) => portList.Add(new V1ServicePort(item.Port, name: $"ExposedPort-{item.Port}-{item.Protocol.ToLower()}", protocol: item.Protocol))));
                }

                // Handle HostConfig PortBindings entries
                if (moduleWithDockerConfig.Config?.CreateOptions?.HostConfig?.PortBindings != null)
                {
                    foreach (KeyValuePair <string, IList <DockerModels.PortBinding> > portBinding in moduleWithDockerConfig.Config?.CreateOptions?.HostConfig?.PortBindings)
                    {
                        string[] portProtocol = portBinding.Key.Split('/');
                        if (portProtocol.Length == 2)
                        {
                            if (int.TryParse(portProtocol[0], out int port) && this.ValidateProtocol(portProtocol[1], out string protocol))
                            {
                                // Entries in Docker portMap wants to expose a port on the host (hostPort) and map it to the container's port (port)
                                // We interpret that as the pod wants the cluster to expose a port on a public IP (hostPort), and target it to the container's port (port)
                                foreach (DockerModels.PortBinding hostBinding in portBinding.Value)
                                {
                                    if (int.TryParse(hostBinding.HostPort, out int hostPort))
                                    {
                                        // If a port entry contains the same "port", then remove it and replace with a new ServicePort that contains a target.
                                        var duplicate = portList.SingleOrDefault(a => a.Port == hostPort);
                                        if (duplicate != default(V1ServicePort))
                                        {
                                            portList.Remove(duplicate);
                                        }

                                        portList.Add(new V1ServicePort(hostPort, name: $"HostPort-{port}-{protocol.ToLower()}", protocol: protocol, targetPort: port));
                                        onlyExposedPorts = false;
                                    }
                                    else
                                    {
                                        Events.PortBindingValue(module, portBinding.Key);
                                    }
                                }
                            }
                        }
                    }
                }
            }

            if (portList.Count > 0)
            {
                // Selector: by module name and device name, also how we will label this puppy.
                var objectMeta = new V1ObjectMeta(annotations: serviceAnnotations.GetOrElse(() => null), labels: labels, name: KubeUtils.SanitizeDNSValue(moduleIdentity.ModuleId));
                // How we manage this service is dependent on the port mappings user asks for.
                // If the user tells us to only use ClusterIP ports, we will always set the type to ClusterIP.
                // If all we had were exposed ports, we will assume ClusterIP. Otherwise, we use the given value as the default service type
                //
                // If the user wants to expose the ClusterIPs port externally, they should manually create a service to expose it.
                // This gives the user more control as to how they want this to work.
                string serviceType;
                if (onlyExposedPorts)
                {
                    serviceType = "ClusterIP";
                }
                else
                {
                    serviceType = this.defaultMapServiceType;
                }

                return(Option.Some(new V1Service(metadata: objectMeta, spec: new V1ServiceSpec(type: serviceType, ports: portList, selector: labels))));
            }
            else
            {
                return(Option.None <V1Service>());
            }
        }
예제 #5
0
        private VolumeOptions GetVolumesFromModule(IModule <AgentDocker.CombinedDockerConfig> moduleWithDockerConfig)
        {
            var v1ConfigMapVolumeSource = new V1ConfigMapVolumeSource(null, null, this.proxyConfigVolumeName, null);

            var volumeList = new List <V1Volume>
            {
                new V1Volume(SocketVolumeName, emptyDir: new V1EmptyDirVolumeSource()),
                new V1Volume(ConfigVolumeName, configMap: v1ConfigMapVolumeSource)
            };
            var proxyMountList = new List <V1VolumeMount>
            {
                new V1VolumeMount(SocketDir, SocketVolumeName)
            };
            var volumeMountList = new List <V1VolumeMount>(proxyMountList);

            proxyMountList.Add(new V1VolumeMount(this.proxyConfigPath, ConfigVolumeName));

            if ((moduleWithDockerConfig.Config?.CreateOptions?.HostConfig?.Binds == null) && (moduleWithDockerConfig.Config?.CreateOptions?.HostConfig?.Mounts == null))
            {
                return(Option.Some((volumeList, proxyMountList, volumeMountList)));
            }

            if (moduleWithDockerConfig.Config?.CreateOptions?.HostConfig?.Binds != null)
            {
                foreach (string bind in moduleWithDockerConfig.Config?.CreateOptions?.HostConfig?.Binds)
                {
                    string[] bindSubstrings = bind.Split(':');
                    if (bindSubstrings.Count() >= 2)
                    {
                        string name      = KubeUtils.SanitizeDNSValue(bindSubstrings[0]);
                        string type      = "DirectoryOrCreate";
                        string hostPath  = bindSubstrings[0];
                        string mountPath = bindSubstrings[1];
                        bool   readOnly  = (bindSubstrings.Count() > 2) && bindSubstrings[2].Contains("ro");
                        volumeList.Add(new V1Volume(name, hostPath: new V1HostPathVolumeSource(hostPath, type)));
                        volumeMountList.Add(new V1VolumeMount(mountPath, name, readOnlyProperty: readOnly));
                    }
                }
            }

            if (moduleWithDockerConfig.Config?.CreateOptions?.HostConfig?.Mounts != null)
            {
                foreach (DockerModels.Mount mount in moduleWithDockerConfig.Config?.CreateOptions?.HostConfig?.Mounts)
                {
                    if (mount.Type.Equals("bind", StringComparison.InvariantCultureIgnoreCase))
                    {
                        string name      = KubeUtils.SanitizeDNSValue(mount.Source);
                        string type      = "DirectoryOrCreate";
                        string hostPath  = mount.Source;
                        string mountPath = mount.Target;
                        bool   readOnly  = mount.ReadOnly;
                        volumeList.Add(new V1Volume(name, hostPath: new V1HostPathVolumeSource(hostPath, type)));
                        volumeMountList.Add(new V1VolumeMount(mountPath, name, readOnlyProperty: readOnly));
                    }
                    else if (mount.Type.Equals("volume", StringComparison.InvariantCultureIgnoreCase))
                    {
                        string name      = KubeUtils.SanitizeDNSValue(mount.Source);
                        string mountPath = mount.Target;
                        bool   readOnly  = mount.ReadOnly;
                        volumeList.Add(new V1Volume(name, emptyDir: new V1EmptyDirVolumeSource()));
                        volumeMountList.Add(new V1VolumeMount(mountPath, name, readOnlyProperty: readOnly));
                    }
                }
            }

            return(volumeList.Count > 0 || volumeMountList.Count > 0
                ? Option.Some((volumeList, proxyMountList, volumeMountList))
                : Option.None <(List <V1Volume>, List <V1VolumeMount>, List <V1VolumeMount>)>());
        }
예제 #6
0
        V1PodTemplateSpec GetPodFromModule(Dictionary <string, string> labels, KubernetesModule <TConfig> module, IModuleIdentity moduleIdentity)
        {
            if (module is IModule <AgentDocker.CombinedDockerConfig> moduleWithDockerConfig)
            {
                // pod labels
                var podLabels = new Dictionary <string, string>(labels);

                // pod annotations
                var podAnnotations = new Dictionary <string, string>();
                podAnnotations.Add(Constants.K8sEdgeOriginalModuleId, moduleIdentity.ModuleId);
                // Convert docker labels to annotations because docker labels don't have the same restrictions as
                // Kuberenetes labels.
                if (moduleWithDockerConfig.Config.CreateOptions?.Labels != null)
                {
                    foreach (KeyValuePair <string, string> label in moduleWithDockerConfig.Config.CreateOptions?.Labels)
                    {
                        podAnnotations.Add(KubeUtils.SanitizeAnnotationKey(label.Key), label.Value);
                    }
                }

                // Per container settings:
                // exposed ports
                Option <List <V1ContainerPort> > exposedPortsOption = (moduleWithDockerConfig.Config?.CreateOptions?.ExposedPorts != null)
                    ? this.GetExposedPorts(moduleWithDockerConfig.Config.CreateOptions.ExposedPorts).Map(
                    servicePorts =>
                    servicePorts.Select(tuple => new V1ContainerPort(tuple.Port, protocol: tuple.Protocol)).ToList())
                    : Option.None <List <V1ContainerPort> >();

                // privileged container
                Option <V1SecurityContext> securityContext = (moduleWithDockerConfig.Config?.CreateOptions?.HostConfig?.Privileged == true) ? Option.Some(new V1SecurityContext(privileged: true)) : Option.None <V1SecurityContext>();

                // Environment Variables.
                List <V1EnvVar> env = this.CollectEnv(moduleWithDockerConfig, (KubernetesModuleIdentity)moduleIdentity);

                // Bind mounts
                (List <V1Volume> volumeList, List <V1VolumeMount> proxyMounts, List <V1VolumeMount> volumeMountList) = this.GetVolumesFromModule(moduleWithDockerConfig).GetOrElse((null, null, null));

                // Image
                string moduleImage = moduleWithDockerConfig.Config.Image;

                var containerList = new List <V1Container>()
                {
                    new V1Container(
                        KubeUtils.SanitizeDNSValue(moduleIdentity.ModuleId),
                        env: env,
                        image: moduleImage,
                        volumeMounts: volumeMountList,
                        securityContext: securityContext.GetOrElse(() => null),
                        ports: exposedPortsOption.GetOrElse(() => null)),

                    // TODO: Add Proxy container here - configmap for proxy configuration.
                    new V1Container(
                        "proxy",
                        env: env, // TODO: check these for validity for proxy.
                        image: this.proxyImage,
                        volumeMounts: proxyMounts)
                };

                Option <List <V1LocalObjectReference> > imageSecret = moduleWithDockerConfig.Config.AuthConfig.Map(
                    auth =>
                {
                    var secret   = new ImagePullSecret(auth);
                    var authList = new List <V1LocalObjectReference>
                    {
                        new V1LocalObjectReference(secret.Name)
                    };
                    return(authList);
                });

                var modulePodSpec = new V1PodSpec(containerList, volumes: volumeList, imagePullSecrets: imageSecret.GetOrElse(() => null));
                var objectMeta    = new V1ObjectMeta(labels: podLabels, annotations: podAnnotations);
                return(new V1PodTemplateSpec(metadata: objectMeta, spec: modulePodSpec));
            }
            else
            {
                Events.InvalidModuleType(module);
            }

            return(new V1PodTemplateSpec());
        }