diff --git a/.github/labeler.yml b/.github/labeler.yml index d3961e5d..46e18233 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -7,6 +7,9 @@ ecs: local: - local/**/* +kube: + - kube/**/* + cli: - cli/**/* diff --git a/kube/charts/kubernetes/kube.go b/kube/charts/kubernetes/kube.go index 6b08d79a..4a531fa8 100644 --- a/kube/charts/kubernetes/kube.go +++ b/kube/charts/kubernetes/kube.go @@ -25,6 +25,7 @@ import ( "time" "github.com/compose-spec/compose-go/types" + "github.com/docker/compose-cli/api/compose" apps "k8s.io/api/apps/v1" core "k8s.io/api/core/v1" resource "k8s.io/apimachinery/pkg/api/resource" @@ -33,6 +34,10 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" ) +const ( + clusterIPHeadless = "None" +) + //MapToKubernetesObjects maps compose project to Kubernetes objects func MapToKubernetesObjects(project *types.Project) (map[string]runtime.Object, error) { objects := map[string]runtime.Object{} @@ -46,13 +51,13 @@ func MapToKubernetesObjects(project *types.Project) (map[string]runtime.Object, } if service.Deploy != nil && service.Deploy.Mode == "global" { - daemonset, err := mapToDaemonset(project, service, project.Name) + daemonset, err := mapToDaemonset(project, service) if err != nil { return nil, err } objects[fmt.Sprintf("%s-daemonset.yaml", service.Name)] = daemonset } else { - deployment, err := mapToDeployment(project, service, project.Name) + deployment, err := mapToDeployment(project, service) if err != nil { return nil, err } @@ -61,7 +66,7 @@ func MapToKubernetesObjects(project *types.Project) (map[string]runtime.Object, for _, vol := range service.Volumes { if vol.Type == "volume" { vol.Source = strings.ReplaceAll(vol.Source, "_", "-") - objects[fmt.Sprintf("%s-persistentvolumeclaim.yaml", vol.Source)] = mapToPVC(service, vol) + objects[fmt.Sprintf("%s-persistentvolumeclaim.yaml", vol.Source)] = mapToPVC(project, service, vol) } } } @@ -70,7 +75,12 @@ func MapToKubernetesObjects(project *types.Project) (map[string]runtime.Object, func mapToService(project *types.Project, service types.ServiceConfig) *core.Service { ports := []core.ServicePort{} + serviceType := core.ServiceTypeClusterIP + clusterIP := "" for _, p := range service.Ports { + if p.Published != 0 { + serviceType = core.ServiceTypeLoadBalancer + } ports = append(ports, core.ServicePort{ Name: fmt.Sprintf("%d-%s", p.Target, strings.ToLower(p.Protocol)), @@ -79,8 +89,8 @@ func mapToService(project *types.Project, service types.ServiceConfig) *core.Ser Protocol: toProtocol(p.Protocol), }) } - if len(ports) == 0 { - return nil + if len(ports) == 0 { // headless service + clusterIP = clusterIPHeadless } return &core.Service{ TypeMeta: meta.TypeMeta{ @@ -91,46 +101,25 @@ func mapToService(project *types.Project, service types.ServiceConfig) *core.Ser Name: service.Name, }, Spec: core.ServiceSpec{ - Selector: map[string]string{"com.docker.compose.service": service.Name}, - Ports: ports, - Type: mapServiceToServiceType(project, service), + ClusterIP: clusterIP, + Selector: selectorLabels(project.Name, service.Name), + Ports: ports, + Type: serviceType, }, } } -func mapServiceToServiceType(project *types.Project, service types.ServiceConfig) core.ServiceType { - serviceType := core.ServiceTypeClusterIP - if len(service.Networks) == 0 { - // service is implicitly attached to "default" network - serviceType = core.ServiceTypeLoadBalancer - } - for name := range service.Networks { - if !project.Networks[name].Internal { - serviceType = core.ServiceTypeLoadBalancer - } - } - for _, port := range service.Ports { - if port.Published != 0 { - serviceType = core.ServiceTypeNodePort - } - } - return serviceType -} - -func mapToDeployment(project *types.Project, service types.ServiceConfig, name string) (*apps.Deployment, error) { - labels := map[string]string{ - "com.docker.compose.service": service.Name, - "com.docker.compose.project": name, - } - podTemplate, err := toPodTemplate(project, service, labels) - if err != nil { - return nil, err - } +func mapToDeployment(project *types.Project, service types.ServiceConfig) (*apps.Deployment, error) { + labels := selectorLabels(project.Name, service.Name) selector := new(meta.LabelSelector) selector.MatchLabels = make(map[string]string) for key, val := range labels { selector.MatchLabels[key] = val } + podTemplate, err := toPodTemplate(project, service, labels) + if err != nil { + return nil, err + } return &apps.Deployment{ TypeMeta: meta.TypeMeta{ Kind: "Deployment", @@ -149,11 +138,15 @@ func mapToDeployment(project *types.Project, service types.ServiceConfig, name s }, nil } -func mapToDaemonset(project *types.Project, service types.ServiceConfig, name string) (*apps.DaemonSet, error) { - labels := map[string]string{ - "com.docker.compose.service": service.Name, - "com.docker.compose.project": name, +func selectorLabels(projectName string, serviceName string) map[string]string { + return map[string]string{ + compose.ProjectTag: projectName, + compose.ServiceTag: serviceName, } +} + +func mapToDaemonset(project *types.Project, service types.ServiceConfig) (*apps.DaemonSet, error) { + labels := selectorLabels(project.Name, service.Name) podTemplate, err := toPodTemplate(project, service, labels) if err != nil { return nil, err @@ -196,7 +189,7 @@ func toDeploymentStrategy(deploy *types.DeployConfig) apps.DeploymentStrategy { } } -func mapToPVC(service types.ServiceConfig, vol types.ServiceVolumeConfig) runtime.Object { +func mapToPVC(project *types.Project, service types.ServiceConfig, vol types.ServiceVolumeConfig) runtime.Object { rwaccess := core.ReadWriteOnce if vol.ReadOnly { rwaccess = core.ReadOnlyMany @@ -208,7 +201,7 @@ func mapToPVC(service types.ServiceConfig, vol types.ServiceVolumeConfig) runtim }, ObjectMeta: meta.ObjectMeta{ Name: vol.Source, - Labels: map[string]string{"com.docker.compose.service": service.Name}, + Labels: selectorLabels(project.Name, service.Name), }, Spec: core.PersistentVolumeClaimSpec{ VolumeName: vol.Source, diff --git a/kube/charts/kubernetes/kube_test.go b/kube/charts/kubernetes/kube_test.go new file mode 100644 index 00000000..148de7bd --- /dev/null +++ b/kube/charts/kubernetes/kube_test.go @@ -0,0 +1,87 @@ +// +build kube + +/* + Copyright 2020 Docker Compose CLI authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package kubernetes + +import ( + "testing" + + "gotest.tools/v3/assert" + + core "k8s.io/api/core/v1" + meta "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" +) + +func TestServiceWithExposedPort(t *testing.T) { + model, err := loadYAML(` +services: + nginx: + image: nginx + ports: + - "80:80" +`) + assert.NilError(t, err) + + service := mapToService(model, model.Services[0]) + assert.DeepEqual(t, *service, core.Service{ + TypeMeta: meta.TypeMeta{ + Kind: "Service", + APIVersion: "v1", + }, + ObjectMeta: meta.ObjectMeta{ + Name: "nginx", + }, + Spec: core.ServiceSpec{ + Selector: map[string]string{"com.docker.compose.service": "nginx", "com.docker.compose.project": ""}, + Ports: []core.ServicePort{ + { + Name: "80-tcp", + Port: int32(80), + TargetPort: intstr.FromInt(int(80)), + Protocol: core.ProtocolTCP, + }, + }, + Type: core.ServiceTypeLoadBalancer, + }}) +} + +func TestServiceWithoutExposedPort(t *testing.T) { + model, err := loadYAML(` +services: + nginx: + image: nginx +`) + assert.NilError(t, err) + + service := mapToService(model, model.Services[0]) + assert.DeepEqual(t, *service, core.Service{ + TypeMeta: meta.TypeMeta{ + Kind: "Service", + APIVersion: "v1", + }, + ObjectMeta: meta.ObjectMeta{ + Name: "nginx", + }, + Spec: core.ServiceSpec{ + Selector: map[string]string{"com.docker.compose.service": "nginx", "com.docker.compose.project": ""}, + ClusterIP: "None", + Ports: []core.ServicePort{}, + Type: core.ServiceTypeClusterIP, + }}) +} diff --git a/kube/e2e/kube-simple-demo/demo_sentences.yaml b/kube/e2e/kube-simple-demo/demo_sentences.yaml new file mode 100644 index 00000000..85107558 --- /dev/null +++ b/kube/e2e/kube-simple-demo/demo_sentences.yaml @@ -0,0 +1,13 @@ +services: + db: + build: aci-demo/db + image: gtardif/sentences-db + + words: + build: aci-demo/words + image: gtardif/sentences-api + web: + build: aci-demo/web + image: gtardif/sentences-web + ports: + - "80:80" diff --git a/local/compose/labels.go b/local/compose/labels.go index 0928f26f..f506a70a 100644 --- a/local/compose/labels.go +++ b/local/compose/labels.go @@ -19,6 +19,7 @@ package compose import ( "fmt" + "github.com/docker/compose-cli/api/compose" "github.com/docker/docker/api/types/filters" ) @@ -26,14 +27,14 @@ const ( containerNumberLabel = "com.docker.compose.container-number" oneoffLabel = "com.docker.compose.oneoff" slugLabel = "com.docker.compose.slug" - projectLabel = "com.docker.compose.project" - volumeLabel = "com.docker.compose.volume" + projectLabel = compose.ProjectTag + volumeLabel = compose.VolumeTag workingDirLabel = "com.docker.compose.project.working_dir" configFilesLabel = "com.docker.compose.project.config_files" - serviceLabel = "com.docker.compose.service" + serviceLabel = compose.ServiceTag versionLabel = "com.docker.compose.version" configHashLabel = "com.docker.compose.config-hash" - networkLabel = "com.docker.compose.network" + networkLabel = compose.NetworkTag //ComposeVersion Compose version ComposeVersion = "1.0-alpha"