diff --git a/Makefile b/Makefile index 66596658..34b400b5 100644 --- a/Makefile +++ b/Makefile @@ -23,16 +23,16 @@ NO_COLOR='\033[0m' clean_world: destroy_shell build_shell up destroy_shell: - docker-compose down -v + docker compose down -v build_shell: - docker-compose build + docker compose build up: - docker-compose up + docker compose up shell: - docker-compose run --service-ports --rm controller bash + docker compose run --service-ports --rm controller bash lint: golangci-lint run diff --git a/configs/settings.yml b/configs/settings.yml index 387a3c13..767ce967 100644 --- a/configs/settings.yml +++ b/configs/settings.yml @@ -23,12 +23,16 @@ base: &base startup_probe_failure_threshold: ${STARTUP_PROBE_FAILURE_THRESHOLD:-80} startup_probe_period_seconds: ${STARTUP_PROBE_PERIOD_SECONDS:-15} ephemeral_storage_coefficient: ${EPHEMERAL_STORAGE_COEFFICIENT:-1.9} - cert_manager_cluster_issuer: ${CERT_MANAGER_CLUSTER_ISSUER:-letsencrypt} + cert_manager_cluster_issuer: ${CERT_MANAGER_CLUSTER_ISSUER:-} + sandbox_enabled: ${SANDBOX_ENABLED} ingress_default_port: 443 + pvc_storage_class_name: uffizzi-standard service_checks: ip_ping_timeout: 1800s availability_timeout: 1800s await_status_timeout: 1800s + await_rolling_update_timeout: 900s + step_awaiting_rolling_update: 6s per_address_attempts: 180 per_address_timeout: 1800s diff --git a/go.mod b/go.mod index 98720e8d..53df89fb 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,7 @@ require ( ) require ( + github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962 // indirect github.com/KyleBanks/depth v1.2.1 // indirect github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect @@ -42,6 +43,7 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2 // indirect + golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f // indirect golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect diff --git a/go.sum b/go.sum index b98bd0ef..30584c19 100644 --- a/go.sum +++ b/go.sum @@ -48,6 +48,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo= +github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962 h1:KeNholpO2xKjgaaSyd+DyQRrsQjhbSeS7qe4nEw8aQw= +github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962/go.mod h1:kC29dT1vFpj7py2OvG1khBdQpo3kInWP+6QipLbdngo= github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= @@ -371,6 +373,8 @@ github.com/swaggo/http-swagger v1.2.5 h1:iDWoHpJMLNo4nwGOPXsOoqlB9wB6M4xgjhws8x3 github.com/swaggo/http-swagger v1.2.5/go.mod h1:CcoICgY3yVDk2u1LQUCMHbAj0fjlxIX+873psXlIKNA= github.com/swaggo/swag v1.7.9 h1:6vCG5mm43ebDzGlZPMGYrYI4zKFfOr5kicQX8qjeDwc= github.com/swaggo/swag v1.7.9/go.mod h1:gZ+TJ2w/Ve1RwQsA2IRoSOTidHz6DX+PIG8GWvbnoLU= +github.com/tg123/go-htpasswd v1.2.0 h1:UKp34m9H467/xklxUxU15wKRru7fwXoTojtxg25ITF0= +github.com/tg123/go-htpasswd v1.2.0/go.mod h1:h7IzlfpvIWnVJhNZ0nQ9HaFxHb7pn5uFJYLlEUJa2sM= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= @@ -405,6 +409,7 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -415,6 +420,7 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= diff --git a/internal/clients/kuber/deployment.go b/internal/clients/kuber/deployment.go index a6816c90..de0a71bb 100644 --- a/internal/clients/kuber/deployment.go +++ b/internal/clients/kuber/deployment.go @@ -15,29 +15,13 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" ) -func prepareContainerEnvironmentVariables(container domainTypes.Container) []corev1.EnvVar { - var variables []corev1.EnvVar - - for _, variable := range container.Variables { - newVariable := corev1.EnvVar{Name: variable.Name, Value: variable.Value} - variables = append(variables, newVariable) - } - - for _, secretVariable := range container.SecretVariables { - newSectetVariable := corev1.EnvVar{Name: secretVariable.Name, Value: secretVariable.Value} - variables = append(variables, newSectetVariable) - } - - return variables -} - func (client *Client) FindDeployment(namespaceName, name string) (*appsv1.Deployment, error) { deployments := client.clientset.AppsV1().Deployments(namespaceName) return deployments.Get(client.context, name, metav1.GetOptions{}) } -func (client *Client) findOrInitializeDeployment(namespace *corev1.Namespace, deploymentName, deploymentSelectorName string) (*appsv1.Deployment, error) { +func (client *Client) findOrInitializeDeployment(namespace *corev1.Namespace, deploymentName string) (*appsv1.Deployment, error) { deployments := client.clientset.AppsV1().Deployments(namespace.Name) deployment, err := deployments.Get(client.context, deploymentName, metav1.GetOptions{}) @@ -47,7 +31,7 @@ func (client *Client) findOrInitializeDeployment(namespace *corev1.Namespace, de } if len(deployment.UID) == 0 { - deployment = initializeDeployment(namespace, deploymentName, deploymentSelectorName) + deployment = initializeDeployment(namespace, deploymentName) } return deployment, nil @@ -96,7 +80,6 @@ func (client *Client) updateDeploymentAttributes( namespace *corev1.Namespace, deployment *appsv1.Deployment, containerList domainTypes.ContainerList, - resources []domainTypes.Resource, ) (*appsv1.Deployment, error) { var containers []corev1.Container @@ -151,6 +134,16 @@ func (client *Client) updateDeploymentAttributes( corev1.ResourceCPU: *podCpuLimit, } + name := global.Settings.ResourceName.ContainerSecret(draftContainer.ID) + secret, err := client.GetSecret(namespace.Name, name) + + if err != nil && !errors.IsNotFound(err) { + return nil, err + } + + containerVariables := prepareContainerEnvironmentVariables(draftContainer) + containerSecrets := prepareContainerSecrets(draftContainer, secret) + container := corev1.Container{ Name: draftContainer.ControllerName, Image: containerImage, @@ -160,8 +153,7 @@ func (client *Client) updateDeploymentAttributes( Requests: requests, Limits: limits, }, - Env: prepareContainerEnvironmentVariables(draftContainer), - EnvFrom: prepareEnvFromResources(resources), + Env: append(containerVariables, containerSecrets...), VolumeMounts: prepareContainerVolumeMounts(draftContainer), LivenessProbe: prepareContainerHealthcheck(draftContainer), } @@ -198,19 +190,18 @@ func (client *Client) updateDeploymentAttributes( func (client *Client) CreateOrUpdateDeployments( namespace *corev1.Namespace, - deploymentName, deploymentSelectorName string, + deploymentName string, containerList domainTypes.ContainerList, credentials []domainTypes.Credential, - resources []domainTypes.Resource, ) (*appsv1.Deployment, error) { deployments := client.clientset.AppsV1().Deployments(namespace.Name) - deployment, err := client.findOrInitializeDeployment(namespace, deploymentName, deploymentSelectorName) + deployment, err := client.findOrInitializeDeployment(namespace, deploymentName) if err != nil { return nil, err } - deployment, err = client.updateDeploymentAttributes(namespace, deployment, containerList, resources) + deployment, err = client.updateDeploymentAttributes(namespace, deployment, containerList) if err != nil { return nil, err } diff --git a/internal/clients/kuber/deployment_utils.go b/internal/clients/kuber/deployment_utils.go index f06d8ad7..8ddebd21 100644 --- a/internal/clients/kuber/deployment_utils.go +++ b/internal/clients/kuber/deployment_utils.go @@ -1,51 +1,115 @@ package kuber import ( + "fmt" + "log" + "strings" + "gitlab.com/dualbootpartners/idyl/uffizzi_controller/internal/global" domainTypes "gitlab.com/dualbootpartners/idyl/uffizzi_controller/internal/types/domain" corev1 "k8s.io/api/core/v1" ) -func prepareEnvFromResources(resources []domainTypes.Resource) []corev1.EnvFromSource { - environmentsFrom := []corev1.EnvFromSource{} - - for _, resource := range resources { - envFrom := prepareEnvFromResource(resource) +func prepareContainerEnvironmentVariables(container domainTypes.Container) []corev1.EnvVar { + var variables []corev1.EnvVar - environmentsFrom = append(environmentsFrom, *envFrom) + for _, variable := range container.Variables { + newVariable := corev1.EnvVar{Name: variable.Name, Value: variable.Value} + variables = append(variables, newVariable) } - return environmentsFrom + return variables } -func prepareEnvFromResource(resource domainTypes.Resource) *corev1.EnvFromSource { - resourceName := global.Settings.ResourceName.Resource(resource.ID) +func prepareContainerSecrets(container domainTypes.Container, secret *corev1.Secret) []corev1.EnvVar { + envVariables := []corev1.EnvVar{} - if resource.Kind == domainTypes.ResourceKindConfigMap { - return &corev1.EnvFromSource{ - ConfigMapRef: &corev1.ConfigMapEnvSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: resourceName, - }, - }, - } + if secret == nil { + log.Printf("No Secret Variables (containerId=%d)", container.ID) + return envVariables } - if resource.Kind == domainTypes.ResourceKindSecret { - return &corev1.EnvFromSource{ - SecretRef: &corev1.SecretEnvSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: resourceName, + name := global.Settings.ResourceName.ContainerSecret(container.ID) + log.Printf("Container Secret Name - %s", name) + + for envVariableName := range secret.Data { + envVariable := corev1.EnvVar{ + Name: envVariableName, + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + Key: envVariableName, + LocalObjectReference: corev1.LocalObjectReference{ + Name: name, + }, }, }, } + + envVariables = append(envVariables, envVariable) } - return nil + return envVariables } func prepareDeploymentVolumes(containerList domainTypes.ContainerList) []corev1.Volume { volumes := []corev1.Volume{} + configFileVolumes := prepareDeploymentConfigFileVolumes(containerList) + volumes = append(volumes, configFileVolumes...) + pvcVolumes := prepareDeploymentPvcVolumes(containerList) + volumes = append(volumes, pvcVolumes...) + + return volumes +} + +func prepareContainerVolumeMounts(container domainTypes.Container) []corev1.VolumeMount { + volumeMounts := []corev1.VolumeMount{} + configVolumeMounts := prepareContainerConfigFileVolumeMounts(container) + volumeMounts = append(volumeMounts, configVolumeMounts...) + + namedVolumeMounts := prepareContainerNamedVolumeMounts(container) + volumeMounts = append(volumeMounts, namedVolumeMounts...) + + anonymousVolumeMounts := prepareContainerAnonymousVolumeMounts(container) + volumeMounts = append(volumeMounts, anonymousVolumeMounts...) + + return volumeMounts +} + +func prepareConfigFileMountPath(containerConfigFile *domainTypes.ContainerConfigFile) (string, string) { + mountPath := containerConfigFile.MountPath + + if len(mountPath) == 0 { + mountPath = "/" + } + + if mountPath[0] != '/' { + mountPath = fmt.Sprintf("/%v", containerConfigFile.MountPath) + } + + if mountPath == "/" { + mountPath = fmt.Sprintf("/%v", containerConfigFile.ConfigFile.Filename) + } + + mountPathParts := strings.Split(mountPath, "/") + mountFileName := mountPathParts[len(mountPathParts)-1] + + return mountPath, mountFileName +} + +func prepareCredentialsDeployment(credentials []domainTypes.Credential) []corev1.LocalObjectReference { + references := []corev1.LocalObjectReference{} + + for _, credential := range credentials { + credentialName := global.Settings.ResourceName.Credential(credential.ID) + + references = append(references, corev1.LocalObjectReference{Name: credentialName}) + } + + return references +} + +func prepareDeploymentConfigFileVolumes(containerList domainTypes.ContainerList) []corev1.Volume { + volumes := []corev1.Volume{} for _, container := range containerList.Items { for _, containerConfigFile := range container.ContainerConfigFiles { @@ -57,8 +121,10 @@ func prepareDeploymentVolumes(containerList domainTypes.ContainerList) []corev1. volume := corev1.Volume{Name: volumeName} + _, mountFileName := prepareConfigFileMountPath(containerConfigFile) + items := []corev1.KeyToPath{ - {Key: configFile.Filename, Path: configFile.Filename}, + {Key: configFile.Filename, Path: mountFileName}, } if configFile.Kind == domainTypes.ConfigFileKindConfigMap { @@ -84,7 +150,28 @@ func prepareDeploymentVolumes(containerList domainTypes.ContainerList) []corev1. return volumes } -func prepareContainerVolumeMounts(container domainTypes.Container) []corev1.VolumeMount { +func prepareDeploymentPvcVolumes(containerList domainTypes.ContainerList) []corev1.Volume { + volumes := []corev1.Volume{} + pvcVolumes := containerList.GetUniqNamedVolumes() + pvcVolumes = append(pvcVolumes, containerList.GetUniqAnonymousVolumes()...) + + for _, pvcVolume := range pvcVolumes { + volume := corev1.Volume{ + Name: global.Settings.ResourceName.VolumeName(pvcVolume.UniqName), + VolumeSource: corev1.VolumeSource{ + PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: global.Settings.ResourceName.PvcName(pvcVolume.UniqName), + }, + }, + } + + volumes = append(volumes, volume) + } + + return volumes +} + +func prepareContainerConfigFileVolumeMounts(container domainTypes.Container) []corev1.VolumeMount { volumeMounts := []corev1.VolumeMount{} for _, containerConfigFile := range container.ContainerConfigFiles { @@ -92,9 +179,12 @@ func prepareContainerVolumeMounts(container domainTypes.Container) []corev1.Volu volumeName := global.Settings.ResourceName.ContainerVolume(container.ID, configFile.ID) + mountPath, mountFileName := prepareConfigFileMountPath(containerConfigFile) + volumeMount := corev1.VolumeMount{ Name: volumeName, - MountPath: containerConfigFile.MountPath, + MountPath: mountPath, + SubPath: mountFileName, ReadOnly: true, } @@ -104,16 +194,46 @@ func prepareContainerVolumeMounts(container domainTypes.Container) []corev1.Volu return volumeMounts } -func prepareCredentialsDeployment(credentials []domainTypes.Credential) []corev1.LocalObjectReference { - references := []corev1.LocalObjectReference{} +func prepareContainerNamedVolumeMounts(container domainTypes.Container) []corev1.VolumeMount { + volumeMounts := []corev1.VolumeMount{} - for _, credential := range credentials { - credentialName := global.Settings.ResourceName.Credential(credential.ID) + for _, containerVolume := range container.ContainerVolumes { + if !containerVolume.IsNamedType() { + continue + } - references = append(references, corev1.LocalObjectReference{Name: credentialName}) + name := containerVolume.BuildUniqName(&container) + volumeMount := corev1.VolumeMount{ + Name: global.Settings.ResourceName.VolumeName(name), + MountPath: containerVolume.Target, + ReadOnly: containerVolume.ReadOnly, + } + + volumeMounts = append(volumeMounts, volumeMount) } - return references + return volumeMounts +} + +func prepareContainerAnonymousVolumeMounts(container domainTypes.Container) []corev1.VolumeMount { + volumeMounts := []corev1.VolumeMount{} + + for _, containerVolume := range container.ContainerVolumes { + if !containerVolume.IsAnonymousType() { + continue + } + + name := containerVolume.BuildUniqName(&container) + volumeMount := corev1.VolumeMount{ + Name: global.Settings.ResourceName.VolumeName(name), + MountPath: containerVolume.Source, + ReadOnly: containerVolume.ReadOnly, + } + + volumeMounts = append(volumeMounts, volumeMount) + } + + return volumeMounts } func prepareContainerHealthcheck(container domainTypes.Container) *corev1.Probe { diff --git a/internal/clients/kuber/ingress.go b/internal/clients/kuber/ingress.go index d3370311..0e94eba5 100644 --- a/internal/clients/kuber/ingress.go +++ b/internal/clients/kuber/ingress.go @@ -1,10 +1,13 @@ package kuber import ( + "context" + "fmt" "log" "time" "gitlab.com/dualbootpartners/idyl/uffizzi_controller/internal/global" + "gitlab.com/dualbootpartners/idyl/uffizzi_controller/internal/pkg/basic_auth_utils" domainTypes "gitlab.com/dualbootpartners/idyl/uffizzi_controller/internal/types/domain" corev1 "k8s.io/api/core/v1" networkingV1 "k8s.io/api/networking/v1" @@ -12,6 +15,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +const NAMESPACE_ANNOTATION_KEY = "ingressName" +const BASIC_AUTH_SECRET_NAME = "basic-auth" + func (client *Client) GetIngresses(namespace string) (*networkingV1.IngressList, error) { ingresses := client.clientset.NetworkingV1().Ingresses(namespace) @@ -45,9 +51,7 @@ func (client *Client) findOrInitializeIngress(namespace, ingressName string) (*n } func (client *Client) GetOrPrepareIngressName(namespace *corev1.Namespace) (string, error) { - var annotationKey = "ingressName" - - ingressName := namespace.Annotations[annotationKey] + ingressName := client.GetIngressName(namespace) if len(ingressName) > 0 { return ingressName, nil @@ -55,23 +59,45 @@ func (client *Client) GetOrPrepareIngressName(namespace *corev1.Namespace) (stri ingressName = generateIngressName() - _, err := client.UpdateAnnotationNamespace(namespace.Name, annotationKey, ingressName) + _, err := client.UpdateAnnotationNamespace(namespace.Name, NAMESPACE_ANNOTATION_KEY, ingressName) return ingressName, err } +func (client *Client) GetIngressName(namespace *corev1.Namespace) string { + ingressName := namespace.Annotations[NAMESPACE_ANNOTATION_KEY] + + return ingressName +} + func (client *Client) UpdateIngressAttributes( ingress *networkingV1.Ingress, namespace *corev1.Namespace, container domainTypes.Container, - serviceName, deploymentHost string) *networkingV1.Ingress { + serviceName, deploymentHost string, + project domainTypes.Project) (*networkingV1.Ingress, error) { containerPort := *container.Port + tls := []networkingV1.IngressTLS{ + {Hosts: []string{deploymentHost}}, + } + + var err error ingress.ObjectMeta.Annotations["kubernetes.io/ingress.class"] = "nginx" - ingress.ObjectMeta.Annotations["cert-manager.io/cluster-issuer"] = global.Settings.CertManagerClusterIssuer - tls := []networkingV1.IngressTLS{ - {Hosts: []string{deploymentHost}, SecretName: deploymentHost}, + if project.IsPreviewsProtected { + ingress, err = client.AddBasicAuthToIngress(ingress, project, namespace.Name) + } + + if err != nil { + return nil, err + } + // if a `ClusterIssuer` is specified, use it. + if len(global.Settings.CertManagerClusterIssuer) > 0 { + ingress.ObjectMeta.Annotations["cert-manager.io/cluster-issuer"] = global.Settings.CertManagerClusterIssuer + tls = []networkingV1.IngressTLS{ + {Hosts: []string{deploymentHost}, SecretName: deploymentHost}, + } } ingressBackend := networkingV1.IngressBackend{ @@ -104,13 +130,14 @@ func (client *Client) UpdateIngressAttributes( ingress.Spec = networkingV1.IngressSpec{TLS: tls, Rules: rules} - return ingress + return ingress, nil } func (client *Client) CreateOrUpdateIngress(namespace *corev1.Namespace, serviceName string, container domainTypes.Container, - deploymentHost string) (*networkingV1.Ingress, error) { + deploymentHost string, + project domainTypes.Project) (*networkingV1.Ingress, error) { ingressName, err := client.GetOrPrepareIngressName(namespace) if err != nil { return nil, err @@ -123,7 +150,11 @@ func (client *Client) CreateOrUpdateIngress(namespace *corev1.Namespace, return ingress, err } - ingress = client.UpdateIngressAttributes(ingress, namespace, container, serviceName, deploymentHost) + ingress, err = client.UpdateIngressAttributes(ingress, namespace, container, serviceName, deploymentHost, project) + + if err != nil { + return ingress, err + } if len(ingress.UID) > 0 { ingress, err = ingresses.Update(client.context, ingress, metav1.UpdateOptions{}) @@ -135,26 +166,52 @@ func (client *Client) CreateOrUpdateIngress(namespace *corev1.Namespace, } func (client *Client) AwaitIngressStatus(inputIngress *networkingV1.Ingress) (*networkingV1.Ingress, error) { - ingresses := client.clientset.NetworkingV1().Ingresses(inputIngress.Namespace) - - for { - ingress, err := ingresses.Get(client.context, inputIngress.Name, metav1.GetOptions{}) - if err != nil { - return nil, err - } - - if len(ingress.Status.LoadBalancer.Ingress) > 0 { - return ingress, nil - } - - select { - case <-time.After(global.Settings.ServiceChecks.AwaitStatusTimeout): - return nil, nil - default: - log.Printf("waiting %v seconds for ingress status\n", global.Settings.ResourceRequestBackOffPeriod) - - time.Sleep(global.Settings.ResourceRequestBackOffPeriod) + namespace := inputIngress.Namespace + ctx := context.TODO() + ctx, cancelContext := context.WithCancel(ctx) + ingressChan := make(chan *networkingV1.Ingress, 1) + errorChan := make(chan error, 1) + + defer cancelContext() + + go func() { + for { + select { + case <-ctx.Done(): + log.Printf("namespace/%s AwaitIngressStatus goroutine was stopped\n", namespace) + return + default: + ingress, err := client.clientset.NetworkingV1().Ingresses(namespace).Get( + client.context, + inputIngress.Name, + metav1.GetOptions{}, + ) + if err != nil { + errorChan <- err + return + } + + if len(ingress.Status.LoadBalancer.Ingress) > 0 { + ingressChan <- ingress + return + } + + resourceRequestBackOffPeriod := global.Settings.ResourceRequestBackOffPeriod + + log.Printf("namespace/%s waiting %v seconds for ingress status\n", namespace, resourceRequestBackOffPeriod) + + time.Sleep(resourceRequestBackOffPeriod) + } } + }() + + select { + case <-time.After(global.Settings.ServiceChecks.AwaitStatusTimeout): + return nil, fmt.Errorf("ingress check timeout") + case ingress := <-ingressChan: + return ingress, nil + case err := <-errorChan: + return nil, err } } @@ -170,3 +227,75 @@ func (client *Client) RemoveIngress(namespace, name string) error { return err } } + +func (client *Client) AddBasicAuthToIngress( + ingress *networkingV1.Ingress, + project domainTypes.Project, + namespaceName string) (*networkingV1.Ingress, error) { + secret, err := client.FindOrInitializeSecret(namespaceName, BASIC_AUTH_SECRET_NAME) + if err != nil { + return nil, err + } + + authPair := basic_auth_utils.GenerateAuthPair(project.PreviewsUserName, project.PreviewsPassword) + secret.StringData = map[string]string{"auth": authPair} + secret.Type = "Opaque" + + if len(secret.UID) > 0 { + _, err = client.UpdateSecret(namespaceName, secret) + if err != nil { + return nil, err + } + } else { + _, err = client.CreateSecret(namespaceName, secret) + if err != nil { + return nil, err + } + } + + basicAuthAnnotations := getBasicAuthAnnotation(BASIC_AUTH_SECRET_NAME) + + for key, val := range basicAuthAnnotations { + ingress.ObjectMeta.Annotations[key] = val + } + + return ingress, nil +} + +func (client *Client) DeleteBasicAuthFromIngress( + ingress *networkingV1.Ingress, + namespaceName string) (*networkingV1.Ingress, error) { + secret, _ := client.GetSecret(namespaceName, BASIC_AUTH_SECRET_NAME) + + if secret != nil && len(secret.UID) > 0 { + err := client.DeleteSecret(namespaceName, BASIC_AUTH_SECRET_NAME) + if err != nil { + return nil, err + } + } + + basicAuthAnnotationKeys := getBasicAuthAnnotation(BASIC_AUTH_SECRET_NAME) + + for key := range basicAuthAnnotationKeys { + delete(ingress.ObjectMeta.Annotations, key) + } + + return ingress, nil +} + +func (client *Client) UpdateIngress( + ingress *networkingV1.Ingress, + namespaceName string) (*networkingV1.Ingress, error) { + ingresses := client.clientset.NetworkingV1().Ingresses(namespaceName) + ingress, err := ingresses.Update(client.context, ingress, metav1.UpdateOptions{}) + + return ingress, err +} + +func getBasicAuthAnnotation(secretName string) map[string]string { + return map[string]string{ + "nginx.ingress.kubernetes.io/auth-type": "basic", + "nginx.ingress.kubernetes.io/auth-secret": secretName, + "nginx.ingress.kubernetes.io/auth-realm": "Authentication Required", + } +} diff --git a/internal/clients/kuber/persistent_volume_claim.go b/internal/clients/kuber/persistent_volume_claim.go new file mode 100644 index 00000000..bd313e1c --- /dev/null +++ b/internal/clients/kuber/persistent_volume_claim.go @@ -0,0 +1,97 @@ +package kuber + +import ( + "fmt" + + "gitlab.com/dualbootpartners/idyl/uffizzi_controller/internal/global" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func (client Client) FindOrInitializePersistentVolumeClaim( + namespace, name string) (*corev1.PersistentVolumeClaim, error) { + persistentVolumeClaim, err := client.GetPersistentVolumeClaim(namespace, name) + if err != nil && !errors.IsNotFound(err) { + return persistentVolumeClaim, err + } + + if persistentVolumeClaim != nil && len(persistentVolumeClaim.UID) > 0 { + return persistentVolumeClaim, nil + } + + var storageClassName string = global.Settings.PvcStorageClassName + + persistentVolumeClaimDraft := &corev1.PersistentVolumeClaim{ + Spec: corev1.PersistentVolumeClaimSpec{ + StorageClassName: &storageClassName, + AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce}, + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceStorage: resource.MustParse("1Gi"), + }, + }, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Labels: map[string]string{ + "app.kubernetes.io/managed-by": global.Settings.ManagedApplication, + }, + }, + } + + return persistentVolumeClaimDraft, nil +} + +func (client *Client) GetPersistentVolumeClaim(namespace, name string) (*corev1.PersistentVolumeClaim, error) { + persistentVolumeClaimClient := client.clientset.CoreV1().PersistentVolumeClaims(namespace) + + persistentVolumeClaim, err := persistentVolumeClaimClient.Get(client.context, name, metav1.GetOptions{}) + if err != nil { + return nil, err + } + + return persistentVolumeClaim, nil +} + +func (client *Client) GetPersistentVolumeClaims(namespace string) ([]corev1.PersistentVolumeClaim, error) { + persistentVolumeClaimClient := client.clientset.CoreV1().PersistentVolumeClaims(namespace) + + persistentVolumeClaimList, err := persistentVolumeClaimClient.List(client.context, metav1.ListOptions{ + LabelSelector: fmt.Sprintf("app.kubernetes.io/managed-by=%v", global.Settings.ManagedApplication), + }) + if err != nil { + return nil, err + } + + return persistentVolumeClaimList.Items, nil +} + +func (client *Client) CreatePersistentVolumeClaim( + namespace string, + persistentVolumeClaimDraft *corev1.PersistentVolumeClaim, +) (*corev1.PersistentVolumeClaim, error) { + persistentVolumeClaimClient := client.clientset.CoreV1().PersistentVolumeClaims(namespace) + + persistentVolumeClaim, err := persistentVolumeClaimClient.Create( + client.context, persistentVolumeClaimDraft, metav1.CreateOptions{}) + + if err != nil { + return nil, err + } + + return persistentVolumeClaim, nil +} + +func (client *Client) DeletePersistentVolumeClaim(namespace, name string) error { + persistentVolumeClaimClient := client.clientset.CoreV1().PersistentVolumeClaims(namespace) + + err := persistentVolumeClaimClient.Delete(client.context, name, metav1.DeleteOptions{}) + if err != nil { + return nil + } + + return nil +} diff --git a/internal/clients/kuber/pods.go b/internal/clients/kuber/pods.go index 1b30e4c9..ee1fb38b 100644 --- a/internal/clients/kuber/pods.go +++ b/internal/clients/kuber/pods.go @@ -2,16 +2,20 @@ package kuber import ( "bytes" + "fmt" "io" "strings" + "gitlab.com/dualbootpartners/idyl/uffizzi_controller/internal/global" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" ) func (client *Client) GetPods(namespace string) (*v1.PodList, error) { - pods, err := client.clientset.CoreV1().Pods(namespace).List(client.context, metav1.ListOptions{}) + pods, err := client.clientset.CoreV1().Pods(namespace).List(client.context, metav1.ListOptions{ + LabelSelector: fmt.Sprintf("app.kubernetes.io/managed-by=%v", global.Settings.ManagedApplication), + }) return pods, err } diff --git a/internal/clients/kuber/requests.go b/internal/clients/kuber/requests.go index 00439526..36a18b17 100644 --- a/internal/clients/kuber/requests.go +++ b/internal/clients/kuber/requests.go @@ -14,9 +14,7 @@ import ( "k8s.io/utils/pointer" ) -func initializeDeployment( - namespace *corev1.Namespace, - deploymentName, deploymentSelectorName string) *appsv1.Deployment { +func initializeDeployment(namespace *corev1.Namespace, deploymentName string) *appsv1.Deployment { return &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Name: deploymentName, @@ -28,7 +26,7 @@ func initializeDeployment( }, Spec: appsv1.DeploymentSpec{ Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"app": deploymentSelectorName}, + MatchLabels: map[string]string{"app": deploymentName}, }, Strategy: appsv1.DeploymentStrategy{ Type: appsv1.RollingUpdateDeploymentStrategyType, @@ -41,18 +39,14 @@ func initializeDeployment( ObjectMeta: metav1.ObjectMeta{ Name: deploymentName, Labels: map[string]string{ - "app": deploymentSelectorName, + "app": deploymentName, "app.kubernetes.io/managed-by": global.Settings.ManagedApplication, }, }, Spec: corev1.PodSpec{ - Containers: []corev1.Container{}, - Tolerations: []corev1.Toleration{ - { - Key: "sandbox.gke.io/runtime", - Operator: "Exists", - }, - }, + Containers: []corev1.Container{}, + NodeSelector: getPodSpecNodeSelector(), + Tolerations: getPodSpecTolerations(), AutomountServiceAccountToken: pointer.BoolPtr(false), // False. Security, DO NOT REMOVE }, @@ -133,3 +127,26 @@ func initializeHorizontalPodAutoscaler( }, } } + +func getPodSpecNodeSelector() map[string]string { + if global.Settings.SandboxEnabled { + return map[string]string{ + "sandbox.gke.io/runtime": "gvisor", + } + } + + return nil +} + +func getPodSpecTolerations() []corev1.Toleration { + if global.Settings.SandboxEnabled { + return []corev1.Toleration{ + { + Key: "sandbox.gke.io/runtime", + Operator: "Exists", + }, + } + } + + return nil +} diff --git a/internal/clients/kuber/secrets.go b/internal/clients/kuber/secrets.go index ae2ef63f..6d9c8339 100644 --- a/internal/clients/kuber/secrets.go +++ b/internal/clients/kuber/secrets.go @@ -9,7 +9,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -func (client Client) FindOrInitializeSecret(namespace, name string) (*corev1.Secret, error) { +func (client Client) FindOrInitializeSecret(namespace string, name string) (*corev1.Secret, error) { secret, err := client.GetSecret(namespace, name) if err != nil && !errors.IsNotFound(err) { return secret, err diff --git a/internal/clients/kuber/service.go b/internal/clients/kuber/service.go index faab6660..1fc013f6 100644 --- a/internal/clients/kuber/service.go +++ b/internal/clients/kuber/service.go @@ -1,6 +1,8 @@ package kuber import ( + "context" + "fmt" "log" "time" @@ -146,26 +148,46 @@ func (client *Client) CreateOrUpdateService(namespace *corev1.Namespace, deploym } func (client *Client) AwaitServiceStatus(inputService *corev1.Service) (*corev1.Service, error) { - services := client.clientset.CoreV1().Services(inputService.Namespace) - - for { - service, err := services.Get(client.context, inputService.Name, metav1.GetOptions{}) - if err != nil { - return nil, err - } - - if len(service.Spec.ClusterIP) > 0 { - return service, nil - } - - select { - case <-time.After(global.Settings.ServiceChecks.AwaitStatusTimeout): - return nil, nil - default: - log.Printf("waiting %v seconds for service status\n", global.Settings.ResourceRequestBackOffPeriod) - - time.Sleep(global.Settings.ResourceRequestBackOffPeriod) + namespace := inputService.Namespace + ctx := context.TODO() + ctx, cancelContext := context.WithCancel(ctx) + serviceChan := make(chan *corev1.Service, 1) + errorChan := make(chan error, 1) + + defer cancelContext() + + go func() { + for { + select { + case <-ctx.Done(): + log.Printf("namespace/%s AwaitServiceStatus goroutine was stopped\n", namespace) + return + default: + service, err := client.clientset.CoreV1().Services(namespace).Get(client.context, inputService.Name, metav1.GetOptions{}) + if err != nil { + errorChan <- err + return + } + + if len(service.Spec.ClusterIP) > 0 { + serviceChan <- service + return + } + + log.Printf("namespace/%s waiting %v seconds for service status\n", namespace, global.Settings.ResourceRequestBackOffPeriod) + + time.Sleep(global.Settings.ResourceRequestBackOffPeriod) + } } + }() + + select { + case <-time.After(global.Settings.ServiceChecks.AwaitStatusTimeout): + return nil, fmt.Errorf("loadbalancer check timeout") + case service := <-serviceChan: + return service, nil + case err := <-errorChan: + return nil, err } } diff --git a/internal/config/settings/settings.go b/internal/config/settings/settings.go index e54d1fbf..ce1961de 100644 --- a/internal/config/settings/settings.go +++ b/internal/config/settings/settings.go @@ -32,15 +32,19 @@ type Settings struct { StartupProbeDelaySeconds int32 `yaml:"startup_probe_delay_seconds"` StartupProbeFailureThreshold int32 `yaml:"startup_probe_failure_threshold"` StartupProbePeriodSettings int32 `yaml:"startup_probe_period_seconds"` + SandboxEnabled bool `yaml:"sandbox_enabled"` CertManagerClusterIssuer string `yaml:"cert_manager_cluster_issuer"` EphemeralStorageCoefficient float64 `yaml:"ephemeral_storage_coefficient"` IngressDefaultPort int `yaml:"ingress_default_port"` + PvcStorageClassName string `yaml:"pvc_storage_class_name"` } type ServiceChecksSettings struct { - IPPingTimeout time.Duration `yaml:"ip_ping_timeout"` - AvailabilityTimeout time.Duration `yaml:"availability_timeout"` - AwaitStatusTimeout time.Duration `yaml:"await_status_timeout"` - PerAddressAttempts uint `yaml:"per_address_attempts"` - PerAddressTimeout time.Duration `yaml:"per_address_timeout"` + IPPingTimeout time.Duration `yaml:"ip_ping_timeout"` + AvailabilityTimeout time.Duration `yaml:"availability_timeout"` + AwaitStatusTimeout time.Duration `yaml:"await_status_timeout"` + AwaitRollingUpdateTimeout time.Duration `yaml:"await_rolling_update_timeout"` + StepAwaitingRollingUpdate time.Duration `yaml:"step_awaiting_rolling_update"` + PerAddressAttempts uint `yaml:"per_address_attempts"` + PerAddressTimeout time.Duration `yaml:"per_address_timeout"` } diff --git a/internal/domain_logic/checks.go b/internal/domain_logic/checks.go index 4891cd19..07ac12da 100644 --- a/internal/domain_logic/checks.go +++ b/internal/domain_logic/checks.go @@ -257,6 +257,11 @@ func (l *Logic) CheckResourcesAvailability( } } + err = l.RunAwaitRollingUpdate(namespaceName) + if err != nil { + return err + } + publicContainerList := resources.ContainerList.GetPublicContainerList() requests, countPoints, err := l.BuildResourceAvailabilityRequests(publicContainerList, service, ingress) @@ -322,7 +327,7 @@ func (l *Logic) RunNetworkConnectivityChecks(namespace *corev1.Namespace, select { case <-ctx.Done(): - log.Println("Cancel to check resources availability") + log.Printf("namespace/%s Cancel to check resources availability\n", namespace.Name) return nil case value := <-errorChanel: @@ -333,3 +338,45 @@ func (l *Logic) RunNetworkConnectivityChecks(namespace *corev1.Namespace, return nil } } + +func (l *Logic) RunAwaitRollingUpdate(namespace string) error { + ctx := context.TODO() + ctx, cancelContext := context.WithCancel(ctx) + errorChan := make(chan error, 1) + + defer cancelContext() + + go func() { + for { + select { + case <-ctx.Done(): + log.Printf("namespace/%s RunAwaitRollingUpdate goroutine was stopped\n", namespace) + return + default: + pods, err := l.KuberClient.GetPods(namespace) + if err != nil { + errorChan <- err + return + } + + if isPodsReady(pods.Items) { + errorChan <- nil + return + } + + stepAwaitingRollingUpdate := global.Settings.ServiceChecks.StepAwaitingRollingUpdate + + log.Printf("namespace/%s waiting %v seconds for rolling update status\n", namespace, stepAwaitingRollingUpdate) + + time.Sleep(stepAwaitingRollingUpdate) + } + } + }() + + select { + case <-time.After(global.Settings.ServiceChecks.AwaitRollingUpdateTimeout): + return fmt.Errorf("namespace/%s rolling update check timeout", namespace) + case err := <-errorChan: + return err + } +} diff --git a/internal/domain_logic/containers.go b/internal/domain_logic/containers.go index 6e22b79f..e174904f 100644 --- a/internal/domain_logic/containers.go +++ b/internal/domain_logic/containers.go @@ -1,7 +1,12 @@ package domain import ( + "log" "time" + + "gitlab.com/dualbootpartners/idyl/uffizzi_controller/internal/global" + "gitlab.com/dualbootpartners/idyl/uffizzi_controller/internal/pkg/string_utils" + domainTypes "gitlab.com/dualbootpartners/idyl/uffizzi_controller/internal/types/domain" ) type ContainersUsageMetrics struct { @@ -13,3 +18,117 @@ func (l *Logic) GetDeploymentsContainersUsageMetrics(deploymentIDs []uint64, beg ContainersMemory: 0, }, nil } + +func (l *Logic) ApplyContainerSecrets(namespace string, containerList domainTypes.ContainerList) error { + for _, container := range containerList.Items { + name := global.Settings.ResourceName.ContainerSecret(container.ID) + + secret, err := l.KuberClient.FindOrInitializeSecret(namespace, name) + if err != nil { + return err + } + + secretVariables := map[string]string{} + + for _, secretVariable := range container.SecretVariables { + secretVariables[secretVariable.Name] = secretVariable.Value + } + + secret.StringData = secretVariables + + if len(secret.UID) > 0 { + if len(secret.StringData) > 0 { + _, err = l.KuberClient.UpdateSecret(namespace, secret) + if err != nil { + return err + } + } else { + err = l.KuberClient.DeleteSecret(namespace, secret.Name) + if err != nil { + return err + } + } + } else { + if len(secret.StringData) > 0 { + _, err = l.KuberClient.CreateSecret(namespace, secret) + if err != nil { + return err + } + } + } + } + + return nil +} + +func (l *Logic) ApplyContainersNamedVolumes(namespace string, containerList domainTypes.ContainerList) error { + for _, volume := range containerList.GetUniqNamedVolumes() { + pvcName := global.Settings.ResourceName.PvcName(volume.UniqName) + pvc, err := l.KuberClient.FindOrInitializePersistentVolumeClaim(namespace, pvcName) + + if err != nil { + return err + } + + if len(pvc.UID) == 0 { + _, err = l.KuberClient.CreatePersistentVolumeClaim(namespace, pvc) + } + + if err != nil { + return err + } + } + + return nil +} + +func (l *Logic) ApplyContainersAnonymousVolumes(namespace string, containerList domainTypes.ContainerList) error { + for _, volume := range containerList.GetUniqAnonymousVolumes() { + pvcName := global.Settings.ResourceName.PvcName(volume.UniqName) + pvc, err := l.KuberClient.FindOrInitializePersistentVolumeClaim(namespace, pvcName) + + if err != nil { + return err + } + + if len(pvc.UID) == 0 { + _, err = l.KuberClient.CreatePersistentVolumeClaim(namespace, pvc) + } + + if err != nil { + return err + } + } + + return nil +} + +func (l *Logic) RemoveUnusedContainersVolumes(namespace string, containerList domainTypes.ContainerList) error { + uniqVolumes := containerList.GetUniqNamedVolumes() + uniqVolumes = append(uniqVolumes, containerList.GetUniqAnonymousVolumes()...) + newPersistentVolumeClaimNames := []string{} + + for _, volume := range uniqVolumes { + newPersistentVolumeClaimNames = append(newPersistentVolumeClaimNames, global.Settings.ResourceName.PvcName(volume.UniqName)) + } + + existsPersistentVolumeClaims, err := l.KuberClient.GetPersistentVolumeClaims(namespace) + if err != nil { + return err + } + + for _, existsPersistentVolumeClaim := range existsPersistentVolumeClaims { + if string_utils.Contains(newPersistentVolumeClaimNames, existsPersistentVolumeClaim.Name) { + continue + } + + err := l.KuberClient.DeletePersistentVolumeClaim(namespace, existsPersistentVolumeClaim.Name) + if err != nil { + return nil + } + + log.Printf("%v/pvc %v was deleted\n", namespace, existsPersistentVolumeClaim.Name) + } + + return nil +} diff --git a/internal/domain_logic/deployment.go b/internal/domain_logic/deployment.go index e00b7cce..466f80f5 100644 --- a/internal/domain_logic/deployment.go +++ b/internal/domain_logic/deployment.go @@ -1,7 +1,6 @@ package domain import ( - "fmt" "log" "gitlab.com/dualbootpartners/idyl/uffizzi_controller/internal/global" @@ -40,9 +39,10 @@ func (l *Logic) handleDomainDeploymentError(namespaceName string, domainErr erro return nil } -func (l *Logic) CleaningNamespaceForEmptyContainers(namespace *corev1.Namespace, deploymentName string) error { +func (l *Logic) CleaningNamespaceForEmptyContainers(namespace *corev1.Namespace) error { serviceName := namespace.Annotations["serviceName"] ingressName := namespace.Annotations["ingressName"] + deploymentName := global.Settings.ResourceName.Deployment(namespace.Name) namespace.Annotations["serviceName"] = "" namespace.Annotations["ingressName"] = "" @@ -85,7 +85,7 @@ func (l *Logic) ApplyContainers( containerList domainTypes.ContainerList, credentials []domainTypes.Credential, deploymentHost string, - resources []domainTypes.Resource, + project domainTypes.Project, ) error { namespaceName := l.KubernetesNamespaceName(deploymentID) @@ -101,11 +101,10 @@ func (l *Logic) ApplyContainers( return err } - appName := fmt.Sprintf("app-%s", namespace.Name) - deploymentName := appName - deploymentSelectorName := appName + policyName := global.Settings.ResourceName.Policy(namespace.Name) + deploymentName := global.Settings.ResourceName.Deployment(namespace.Name) - policy, err := l.KuberClient.FindOrCreateNetworkPolicy(namespace.Name, appName) + policy, err := l.KuberClient.FindOrCreateNetworkPolicy(namespace.Name, policyName) if err != nil { return err } @@ -113,18 +112,33 @@ func (l *Logic) ApplyContainers( log.Printf("networkPolicy/%s configured", policy.Name) log.Printf("namespace/%s containerList: %+#v", namespace.Name, containerList) - err = l.ClearOldResources(namespace, resources) + err = l.ClearOldConfigurationFiles(namespace, containerList) if err != nil { return err } - err = l.ClearOldConfigurationFiles(namespace, containerList) + err = l.RemoveUnusedContainersVolumes(namespaceName, containerList) + if err != nil { + return err + } + + err = l.ApplyContainerSecrets(namespaceName, containerList) + if err != nil { + return err + } + + err = l.ApplyContainersNamedVolumes(namespaceName, containerList) + if err != nil { + return err + } + + err = l.ApplyContainersAnonymousVolumes(namespaceName, containerList) if err != nil { return err } if containerList.IsEmpty() { - return l.CleaningNamespaceForEmptyContainers(namespace, appName) + return l.CleaningNamespaceForEmptyContainers(namespace) } err = l.ResetNetworkConnectivityTemplate(namespace, containerList) @@ -132,14 +146,7 @@ func (l *Logic) ApplyContainers( return l.handleDomainDeploymentError(namespace.Name, err) } - deployment, err := l.KuberClient.CreateOrUpdateDeployments( - namespace, - deploymentName, - deploymentSelectorName, - containerList, - credentials, - resources, - ) + deployment, err := l.KuberClient.CreateOrUpdateDeployments(namespace, deploymentName, containerList, credentials) if err != nil { return l.handleDomainDeploymentError(namespace.Name, err) } @@ -183,7 +190,7 @@ func (l *Logic) ApplyContainers( var networkBuilder INetworkBuilder - networkDependencies := NewNetworkDependencies(l, namespace, appName, containerList, deployment, deploymentHost) + networkDependencies := NewNetworkDependencies(l, namespace, containerList, deployment, deploymentHost, project) networkBuilder = NewIngressNetworkBuilder(networkDependencies) @@ -201,3 +208,70 @@ func (l *Logic) ApplyContainers( return nil } + +func (l *Logic) ApplyIngressBasciAuth( + deploymentID uint64, + project domainTypes.Project, +) error { + namespaceName := l.KubernetesNamespaceName(deploymentID) + + namespace, err := l.KuberClient.FindNamespace(namespaceName) + if err != nil { + return err + } + + log.Printf("namespace/%s found", namespace.Name) + + ingressName := l.KuberClient.GetIngressName(namespace) + ingress, err := l.KuberClient.FindIngress(namespace.Name, ingressName) + + if err != nil { + return err + } + + ingressWithBasicAuth, err := l.KuberClient.AddBasicAuthToIngress(ingress, project, namespace.Name) + + if err != nil { + return err + } + + _, err = l.KuberClient.UpdateIngress(ingressWithBasicAuth, namespace.Name) + + if err != nil { + return err + } + + return nil +} + +func (l *Logic) DeleteIngressBasciAuth(deploymentID uint64) error { + namespaceName := l.KubernetesNamespaceName(deploymentID) + + namespace, err := l.KuberClient.FindNamespace(namespaceName) + if err != nil { + return err + } + + log.Printf("namespace/%s found", namespace.Name) + + ingressName := l.KuberClient.GetIngressName(namespace) + ingress, err := l.KuberClient.FindIngress(namespace.Name, ingressName) + + if err != nil { + return err + } + + ingressWithoutBasicAuth, err := l.KuberClient.DeleteBasicAuthFromIngress(ingress, namespace.Name) + + if err != nil { + return err + } + + _, err = l.KuberClient.UpdateIngress(ingressWithoutBasicAuth, namespace.Name) + + if err != nil { + return err + } + + return nil +} diff --git a/internal/domain_logic/network_builders.go b/internal/domain_logic/network_builders.go index ba7e4d1c..7dce7085 100644 --- a/internal/domain_logic/network_builders.go +++ b/internal/domain_logic/network_builders.go @@ -20,27 +20,27 @@ type INetworkBuilder interface { type NetworkDependencies struct { DomainLogic *Logic Namespace *corev1.Namespace - AppName string ContainerList domainTypes.ContainerList Deployment *appsv1.Deployment DeploymentHost string + Project domainTypes.Project } func NewNetworkDependencies( domainLogic *Logic, namespace *corev1.Namespace, - appName string, containerList domainTypes.ContainerList, deployment *appsv1.Deployment, deploymentHost string, + project domainTypes.Project, ) *NetworkDependencies { return &NetworkDependencies{ DomainLogic: domainLogic, Namespace: namespace, - AppName: appName, ContainerList: containerList, Deployment: deployment, DeploymentHost: deploymentHost, + Project: project, } } @@ -78,6 +78,7 @@ func (builder *IngressNetworkBuilder) Create() error { namespace := network.Namespace domainLogic := network.DomainLogic deploymentHost := network.DeploymentHost + project := network.Project deploymentSelector, err := builder.GetDeploymentSelectorName() if err != nil { @@ -110,6 +111,7 @@ func (builder *IngressNetworkBuilder) Create() error { service.Name, generalPublicContainer, deploymentHost, + project, ) if err != nil { return err diff --git a/internal/domain_logic/pods.go b/internal/domain_logic/pods.go index 4ee03bd0..1b39a72f 100644 --- a/internal/domain_logic/pods.go +++ b/internal/domain_logic/pods.go @@ -6,14 +6,9 @@ import ( ) func (l *Logic) GetContainers(deploymentID uint64) ([]v1.Pod, error) { - namespaceName := l.KubernetesNamespaceName(deploymentID) - - namespace, err := l.KuberClient.FindNamespace(namespaceName) - if err != nil { - return nil, err - } + namespace := l.KubernetesNamespaceName(deploymentID) - pods, err := l.KuberClient.GetPods(namespace.Name) + pods, err := l.KuberClient.GetPods(namespace) if err != nil { return nil, err @@ -23,14 +18,9 @@ func (l *Logic) GetContainers(deploymentID uint64) ([]v1.Pod, error) { } func (l *Logic) GetContainersMetrics(deploymentID uint64) ([]v1beta1.PodMetrics, error) { - namespaceName := l.KubernetesNamespaceName(deploymentID) + namespace := l.KubernetesNamespaceName(deploymentID) - namespace, err := l.KuberClient.FindNamespace(namespaceName) - if err != nil { - return nil, err - } - - metrics, err := l.KuberClient.GetPodsMetrics(namespace.Name) + metrics, err := l.KuberClient.GetPodsMetrics(namespace) if err != nil { return nil, err @@ -56,14 +46,9 @@ func (l *Logic) GetPodLogs( func (l *Logic) GetPodEvents( deploymentID uint64, ) (*v1.EventList, error) { - namespaceName := l.KubernetesNamespaceName(deploymentID) - - namespace, err := l.KuberClient.FindNamespace(namespaceName) - if err != nil { - return nil, err - } + namespace := l.KubernetesNamespaceName(deploymentID) - events, err := l.KuberClient.ListEvents(namespace.Name) + events, err := l.KuberClient.ListEvents(namespace) return events, err } diff --git a/internal/domain_logic/pods_utils.go b/internal/domain_logic/pods_utils.go new file mode 100644 index 00000000..c9f3b978 --- /dev/null +++ b/internal/domain_logic/pods_utils.go @@ -0,0 +1,15 @@ +package domain + +import corev1 "k8s.io/api/core/v1" + +func isPodsReady(pods []corev1.Pod) bool { + for _, pod := range pods { + for _, containerStatus := range pod.Status.ContainerStatuses { + if !containerStatus.Ready { + return false + } + } + } + + return true +} diff --git a/internal/domain_logic/resources.go b/internal/domain_logic/resources.go deleted file mode 100644 index 98f6e87e..00000000 --- a/internal/domain_logic/resources.go +++ /dev/null @@ -1,196 +0,0 @@ -package domain - -import ( - "log" - "regexp" - - "gitlab.com/dualbootpartners/idyl/uffizzi_controller/internal/global" - "gitlab.com/dualbootpartners/idyl/uffizzi_controller/internal/pkg/string_utils" - domainTypes "gitlab.com/dualbootpartners/idyl/uffizzi_controller/internal/types/domain" - corev1 "k8s.io/api/core/v1" -) - -func (l *Logic) ApplyResource(deploymentID uint64, resource domainTypes.Resource) error { - namespace := l.KubernetesNamespaceName(deploymentID) - - switch resource.Kind { - case domainTypes.ResourceKindConfigMap: - { - err := l.ApplyResourceAsConfigMap(namespace, resource) - if err != nil { - return err - } - - log.Printf("%v/configMap resource-%v was configured", namespace, resource.ID) - } - case domainTypes.ResourceKindSecret: - { - err := l.ApplyResourceAsSecret(namespace, resource) - if err != nil { - return err - } - - log.Printf("%v/secret resource-%v was configured", namespace, resource.ID) - } - default: - { - log.Printf("%v/configMap resource-%v wasn't configured because of the unknown kind", namespace, resource.ID) - } - } - - return nil -} - -func (l *Logic) ApplyResourceAsConfigMap(namespace string, resource domainTypes.Resource) error { - name := global.Settings.ResourceName.Resource(resource.ID) - - configMap, err := l.KuberClient.FindOrInitializeConfigMap(namespace, name) - if err != nil { - return err - } - - variables := map[string]string{} - - for _, variable := range resource.Variables { - variables[variable.Name] = variable.Value - } - - configMap.Data = variables - - if len(configMap.UID) > 0 { - _, err = l.KuberClient.UpdateConfigMap(namespace, configMap) - if err != nil { - return err - } - } else { - _, err = l.KuberClient.CreateConfigMap(namespace, configMap) - if err != nil { - return err - } - } - - return nil -} - -func (l *Logic) ApplyResourceAsSecret(namespace string, resource domainTypes.Resource) error { - name := global.Settings.ResourceName.Resource(resource.ID) - - secret, err := l.KuberClient.FindOrInitializeSecret(namespace, name) - if err != nil { - return err - } - - secretVariables := map[string]string{} - - for _, secretVariable := range resource.Variables { - secretVariables[secretVariable.Name] = secretVariable.Value - } - - secret.StringData = secretVariables - - if len(secret.UID) > 0 { - _, err = l.KuberClient.UpdateSecret(namespace, secret) - if err != nil { - return err - } - } else { - _, err = l.KuberClient.CreateSecret(namespace, secret) - if err != nil { - return err - } - } - - return nil -} - -func (l *Logic) ClearOldResources(namespace *corev1.Namespace, resources []domainTypes.Resource) error { - err := l.ClearOldSecretResources(namespace, resources) - if err != nil { - return nil - } - - err = l.ClearOldConfigMapResources(namespace, resources) - if err != nil { - return nil - } - - return nil -} - -func (l *Logic) ClearOldSecretResources(namespace *corev1.Namespace, resources []domainTypes.Resource) error { - resourcesInstalled := []string{} - - for _, resource := range resources { - resourceName := global.Settings.ResourceName.Resource(resource.ID) - - if resource.Kind == domainTypes.ResourceKindSecret { - resourcesInstalled = append(resourcesInstalled, resourceName) - } - } - - pattern := global.Settings.ResourceName.Resource("") - - secrets, err := l.KuberClient.GetSecrets(namespace.Name) - if err != nil { - return nil - } - - for _, secret := range secrets { - matched, err := regexp.MatchString(pattern, secret.Name) - if err != nil { - return nil - } - - if !matched || string_utils.Contains(resourcesInstalled, secret.Name) { - continue - } - - err = l.KuberClient.DeleteSecret(namespace.Name, secret.Name) - if err != nil { - return nil - } - - log.Printf("%v/secret %v was deleted\n", namespace.Name, secret.Name) - } - - return nil -} - -func (l *Logic) ClearOldConfigMapResources(namespace *corev1.Namespace, resources []domainTypes.Resource) error { - resourcesInstalled := []string{} - - for _, resource := range resources { - resourceName := global.Settings.ResourceName.Resource(resource.ID) - - if resource.Kind == domainTypes.ResourceKindConfigMap { - resourcesInstalled = append(resourcesInstalled, resourceName) - } - } - - pattern := global.Settings.ResourceName.Resource("") - - configMaps, err := l.KuberClient.GetConfigMaps(namespace.Name) - if err != nil { - return nil - } - - for _, configMap := range configMaps { - matched, err := regexp.MatchString(pattern, configMap.Name) - if err != nil { - return nil - } - - if !matched || string_utils.Contains(resourcesInstalled, configMap.Name) { - continue - } - - err = l.KuberClient.DeleteConfigMap(namespace.Name, configMap.Name) - if err != nil { - return nil - } - - log.Printf("%v/configMap %v was deleted\n", namespace.Name, configMap.Name) - } - - return nil -} diff --git a/internal/http/containers.go b/internal/http/containers.go index b5dd822a..1cd3ebf3 100644 --- a/internal/http/containers.go +++ b/internal/http/containers.go @@ -48,8 +48,8 @@ func (h *Handlers) handleGetContainers(w http.ResponseWriter, r *http.Request) { type applyContainersRequest struct { Containers []domainTypes.Container `json:"containers"` Credentials []domainTypes.Credential `json:"credentials,omitempty"` - Resources []domainTypes.Resource `json:"resources"` DeploymentHost string `json:"deployment_url"` + Project domainTypes.Project `json:"project"` } // @Description Create or Update containers within a Deployment. @@ -89,9 +89,9 @@ func (h *Handlers) handleApplyContainers(w http.ResponseWriter, r *http.Request) containerList := domainTypes.ContainerList{Items: containers} credentials := request.Credentials deploymentHost := request.DeploymentHost - resources := request.Resources + project := request.Project - err = domainLogic.ApplyContainers(deploymentId, containerList, credentials, deploymentHost, resources) + err = domainLogic.ApplyContainers(deploymentId, containerList, credentials, deploymentHost, project) if err != nil { handleDomainError("domainLogic.ApplyContainers", err, localHub) } diff --git a/internal/http/ingress_basic_auth.go b/internal/http/ingress_basic_auth.go new file mode 100644 index 00000000..d328f0e7 --- /dev/null +++ b/internal/http/ingress_basic_auth.go @@ -0,0 +1,88 @@ +package http + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + "strconv" + + "github.com/getsentry/sentry-go" + "github.com/gorilla/mux" + domainTypes "gitlab.com/dualbootpartners/idyl/uffizzi_controller/internal/types/domain" +) + +type applyIngressBasicAuthRequest struct { + Project domainTypes.Project `json:"project"` +} + +// @Description Create or Update containers within a Deployment. +// @Param deploymentId path int true "unique Uffizzi Deployment ID" +// @Param spec body applyIngressBasicAuthRequest true "container specification" +// @Success 200 "OK" +// @Failure 500 "most errors including Not Found" +// @Response 403 "Incorrect Token for HTTP Basic Auth" +// @Security BasicAuth +// @Produce plain +// @Router /deployments/{deploymentId}/containers [post] +func (h *Handlers) handleApplyIngressBasciAuth(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + + deploymentId, err := strconv.ParseUint(vars["deploymentId"], 10, 64) //nolint: gomnd + if err != nil { + handleError(err, w, r) + return + } + + var request applyIngressBasicAuthRequest + + err = json.NewDecoder(r.Body).Decode(&request) + if err != nil { + handleError(err, w, r) + return + } + + log.Printf("Decoded HTTP Request: %+v", request) + + go func(localHub *sentry.Hub) { + localHub.ConfigureScope(func(scope *sentry.Scope) { + scope.SetTag("deploymentId", fmt.Sprint(deploymentId)) + }) + + project := request.Project + + err = domainLogic.ApplyIngressBasciAuth(deploymentId, project) + if err != nil { + handleDomainError("domainLogic.ApplyIngressBasciAuth", err, localHub) + } + }(sentry.CurrentHub().Clone()) +} + +// @Description Create or Update containers within a Deployment. +// @Param deploymentId path int true "unique Uffizzi Deployment ID" +// @Success 200 "OK" +// @Failure 500 "most errors including Not Found" +// @Response 403 "Incorrect Token for HTTP Basic Auth" +// @Security BasicAuth +// @Produce plain +// @Router /deployments/{deploymentId}/containers [post] +func (h *Handlers) handleDeleteIngressBasciAuth(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + + deploymentId, err := strconv.ParseUint(vars["deploymentId"], 10, 64) //nolint: gomnd + if err != nil { + handleError(err, w, r) + return + } + + go func(localHub *sentry.Hub) { + localHub.ConfigureScope(func(scope *sentry.Scope) { + scope.SetTag("deploymentId", fmt.Sprint(deploymentId)) + }) + + err = domainLogic.DeleteIngressBasciAuth(deploymentId) + if err != nil { + handleDomainError("domainLogic.DeleteIngressBasciAuth", err, localHub) + } + }(sentry.CurrentHub().Clone()) +} diff --git a/internal/http/resources.go b/internal/http/resources.go deleted file mode 100644 index d14334da..00000000 --- a/internal/http/resources.go +++ /dev/null @@ -1,48 +0,0 @@ -package http - -import ( - "encoding/json" - "log" - "net/http" - "strconv" - - "github.com/gorilla/mux" - domainTypes "gitlab.com/dualbootpartners/idyl/uffizzi_controller/internal/types/domain" -) - -type ApplyResourceRequest struct { - Resource domainTypes.Resource `json:"resource"` -} - -// @Description Create or update Uffizzi Resource. -// @Param resourceId path int true "unique Uffizzi Resource ID" -// @Param spec body ApplyResourceRequest true "Uffizzi Resource specification" -// @Success 200 "OK" -// @Router /resources/{resourceId} [post] -func (h *Handlers) handleApplyResource(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - - deploymentID, err := strconv.ParseUint(vars["deploymentId"], 10, 64) //nolint: gomnd - if err != nil { - handleError(err, w, r) - return - } - - var request ApplyResourceRequest - - err = json.NewDecoder(r.Body).Decode(&request) - if err != nil { - handleError(err, w, r) - return - } - - log.Printf("Decoded HTTP Request: %+v", request) - - err = domainLogic.ApplyResource(deploymentID, request.Resource) - if err != nil { - handleError(err, w, r) - return - } - - respondWithJSON(w, r, http.StatusOK, nil) -} diff --git a/internal/http/routes.go b/internal/http/routes.go index 0a3d9c2d..6a142776 100644 --- a/internal/http/routes.go +++ b/internal/http/routes.go @@ -16,7 +16,6 @@ func drawRoutes(r *mux.Router, h *Handlers) { r.HandleFunc("/deployments/{deploymentId:[0-9]+}", h.handleCreateNamespace).Methods(http.MethodPost) r.HandleFunc("/deployments/{deploymentId:[0-9]+}", h.handleUpdateNamespace).Methods(http.MethodPut) r.HandleFunc("/deployments/{deploymentId:[0-9]+}", h.handleDeleteNamespace).Methods(http.MethodDelete) - r.HandleFunc("/deployments/{deploymentId:[0-9]+}/resources/{resourceId:[0-9]+}", h.handleApplyResource).Methods(http.MethodPost) r.HandleFunc("/deployments/{deploymentId:[0-9]+}/config_files/{configFileId:[0-9]+}", h.handleApplyConfigFile).Methods(http.MethodPost) r.HandleFunc("/deployments/{deploymentId:[0-9]+}/credentials", h.handleApplyCredential).Methods(http.MethodPost) r.HandleFunc("/deployments/{deploymentId:[0-9]+}/credentials/{credentialId}", h.handleDeleteCredential).Methods(http.MethodDelete) @@ -27,5 +26,7 @@ func drawRoutes(r *mux.Router, h *Handlers) { r.HandleFunc("/deployments/{deploymentId:[0-9]+}/services", h.handleGetServices).Methods(http.MethodGet) r.HandleFunc("/deployments/{deploymentId:[0-9]+}/containers/{containerName}/logs", h.handleGetContainerLogs).Methods(http.MethodGet) r.HandleFunc("/deployments/usage_metrics/containers", h.handleGetContainersUsageMetrics).Methods(http.MethodGet) + r.HandleFunc("/deployments/{deploymentId:[0-9]+}/ingress/basic_auth", h.handleApplyIngressBasciAuth).Methods(http.MethodPost) + r.HandleFunc("/deployments/{deploymentId:[0-9]+}/ingress/basic_auth", h.handleDeleteIngressBasciAuth).Methods(http.MethodDelete) r.PathPrefix("/docs/").Handler(httpSwagger.WrapHandler) } diff --git a/internal/pkg/basic_auth_utils/basic_auth_utils.go b/internal/pkg/basic_auth_utils/basic_auth_utils.go new file mode 100644 index 00000000..79a2f19f --- /dev/null +++ b/internal/pkg/basic_auth_utils/basic_auth_utils.go @@ -0,0 +1,15 @@ +package basic_auth_utils + +import ( + "fmt" + + "github.com/GehirnInc/crypt" + _ "github.com/GehirnInc/crypt/apr1_crypt" +) + +func GenerateAuthPair(login string, password string) string { + crypter := crypt.APR1.New() + hash, _ := crypter.Generate([]byte(password), []byte("")) + + return fmt.Sprintf("%s:%s", login, hash) +} diff --git a/internal/pkg/resource_name_utils/resource_name_utils.go b/internal/pkg/resource_name_utils/resource_name_utils.go index af8ccd9f..cd263428 100644 --- a/internal/pkg/resource_name_utils/resource_name_utils.go +++ b/internal/pkg/resource_name_utils/resource_name_utils.go @@ -1,13 +1,12 @@ package resource_name_utils -import "fmt" +import ( + "fmt" + "regexp" +) type ResouceNameUtils struct{} -func (resouceNameUtils *ResouceNameUtils) Resource(id interface{}) string { - return fmt.Sprintf("resource-%v", id) -} - func (resouceNameUtils *ResouceNameUtils) ConfigFile(id interface{}) string { return fmt.Sprintf("config-file-%v", id) } @@ -19,3 +18,33 @@ func (resouceNameUtils *ResouceNameUtils) ContainerVolume(containerID, configFil func (resouceNameUtils *ResouceNameUtils) Credential(credentialId uint64) string { return fmt.Sprintf("credential-%v", credentialId) } + +func (resouceNameUtils *ResouceNameUtils) ContainerSecret(containerID uint64) string { + return fmt.Sprintf("container-%v-secret", containerID) +} + +func (resouceNameUtils *ResouceNameUtils) Deployment(namespace string) string { + return fmt.Sprintf("app-%v", namespace) +} + +func (resouceNameUtils *ResouceNameUtils) Policy(namespace string) string { + return fmt.Sprintf("app-%v", namespace) +} + +func (resouceNameUtils *ResouceNameUtils) PvcName(name string) string { + rfcName := toRfc(name) + + return fmt.Sprintf("pvc-%v", rfcName) +} + +func (resouceNameUtils *ResouceNameUtils) VolumeName(name string) string { + rfcName := toRfc(name) + + return fmt.Sprintf("volume-%v", rfcName) +} + +func toRfc(str string) string { + regexp := regexp.MustCompile(`(\/|~|\.|_)`) + + return regexp.ReplaceAllString(str, "-") +} diff --git a/internal/types/domain/container.go b/internal/types/domain/container.go index 75aafd95..fc6ecd8b 100644 --- a/internal/types/domain/container.go +++ b/internal/types/domain/container.go @@ -26,6 +26,8 @@ type Container struct { MemoryRequest uint `json:"memory_request"` ContainerConfigFiles []*ContainerConfigFile `json:"container_config_files"` Healthcheck *Healthcheck `json:"healthcheck"` + ContainerVolumes []*ContainerVolume `json:"volumes"` + ServiceName string `json:"service_name"` } func (c Container) IsPublic() bool { diff --git a/internal/types/domain/container_list.go b/internal/types/domain/container_list.go index 52fec1bb..3a734794 100644 --- a/internal/types/domain/container_list.go +++ b/internal/types/domain/container_list.go @@ -6,6 +6,12 @@ type ContainerList struct { Items []Container `json:"items"` } +type DeploymentVolume struct { + Volume *ContainerVolume + Container *Container + UniqName string +} + func (list ContainerList) IsEmpty() bool { return list.Count() == 0 } @@ -61,3 +67,75 @@ func (list ContainerList) GetUserContainerList() ContainerList { func (list *ContainerList) AddContainer(container Container) { list.Items = append(list.Items, container) } + +func (list ContainerList) GetUniqNamedVolumes() []DeploymentVolume { + volumes := []DeploymentVolume{} + + for i, container := range list.Items { + for _, containerVolume := range container.ContainerVolumes { + if containerVolume.Type != ContainerVolumeTypeNamed { + continue + } + + isVolumeExists := false + uniqName := containerVolume.BuildUniqName(&list.Items[i]) + + for _, existsVolume := range volumes { + if existsVolume.UniqName == uniqName { + isVolumeExists = true + break + } + } + + if isVolumeExists { + continue + } + + volume := DeploymentVolume{ + Volume: containerVolume, + Container: &list.Items[i], + UniqName: uniqName, + } + + volumes = append(volumes, volume) + } + } + + return volumes +} + +func (list ContainerList) GetUniqAnonymousVolumes() []DeploymentVolume { + volumes := []DeploymentVolume{} + + for i, container := range list.Items { + for _, containerVolume := range container.ContainerVolumes { + if containerVolume.Type != ContainerVolumeTypeAnonymous { + continue + } + + isVolumeExists := false + uniqName := containerVolume.BuildUniqName(&list.Items[i]) + + for _, existsVolume := range volumes { + if existsVolume.UniqName == uniqName { + isVolumeExists = true + break + } + } + + if isVolumeExists { + continue + } + + volume := DeploymentVolume{ + Volume: containerVolume, + Container: &list.Items[i], + UniqName: uniqName, + } + + volumes = append(volumes, volume) + } + } + + return volumes +} diff --git a/internal/types/domain/container_volume.go b/internal/types/domain/container_volume.go new file mode 100644 index 00000000..1fedce41 --- /dev/null +++ b/internal/types/domain/container_volume.go @@ -0,0 +1,41 @@ +package types + +import "fmt" + +type ContainerVolume struct { + Source string `json:"source"` + Target string `json:"target"` + Type ContainerVolumeType `json:"type"` + ReadOnly bool `json:"read_only"` +} + +type ContainerVolumeType string + +const ( + ContainerVolumeTypeNamed ContainerVolumeType = "named" + ContainerVolumeTypeAnonymous ContainerVolumeType = "anonymous" + ContainerVolumeTypeHost ContainerVolumeType = "host" +) + +func (volume ContainerVolume) BuildUniqName(container *Container) string { + switch volume.Type { + case ContainerVolumeTypeAnonymous: + return fmt.Sprintf("%s-%s", container.ServiceName, volume.Source) + case ContainerVolumeTypeNamed: + return volume.Source + default: + return "" + } +} + +func (volume ContainerVolume) IsHostType() bool { + return volume.Type == ContainerVolumeTypeHost +} + +func (volume ContainerVolume) IsNamedType() bool { + return volume.Type == ContainerVolumeTypeNamed +} + +func (volume ContainerVolume) IsAnonymousType() bool { + return volume.Type == ContainerVolumeTypeAnonymous +} diff --git a/internal/types/domain/project.go b/internal/types/domain/project.go new file mode 100644 index 00000000..2dada55b --- /dev/null +++ b/internal/types/domain/project.go @@ -0,0 +1,7 @@ +package types + +type Project struct { + IsPreviewsProtected bool `json:"is_previews_protected"` + PreviewsPassword string `json:"previews_password"` + PreviewsUserName string `json:"previews_username"` +} diff --git a/internal/types/domain/resource.go b/internal/types/domain/resource.go deleted file mode 100644 index d9439539..00000000 --- a/internal/types/domain/resource.go +++ /dev/null @@ -1,18 +0,0 @@ -package types - -type ResourceKind string - -const ( - ResourceKindConfigMap ResourceKind = "config_map" - ResourceKindSecret ResourceKind = "secret" -) - -type ResourceVariable struct { - Name string `json:"name"` - Value string `json:"value"` -} -type Resource struct { - ID uint64 `json:"id"` - Kind ResourceKind `json:"kind"` - Variables []ResourceVariable `json:"variables,omitempty"` -}