mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-26 19:13:34 +00:00
test: Add an e2e test for build&push (#69)
* test: Add an e2e test for build&push * test: Add image cleanup * test: Switch to assert.NoError
This commit is contained in:
@@ -30,6 +30,7 @@ go.work.sum
|
|||||||
/data/
|
/data/
|
||||||
/dist/
|
/dist/
|
||||||
/uc
|
/uc
|
||||||
|
/tmp
|
||||||
|
|
||||||
# Web
|
# Web
|
||||||
node_modules/
|
node_modules/
|
||||||
|
|||||||
@@ -92,9 +92,6 @@ func runDeploy(ctx context.Context, uncli *cli.CLI, opts deployOptions) error {
|
|||||||
fmt.Println("Not building services as requested.")
|
fmt.Println("Not building services as requested.")
|
||||||
} else {
|
} else {
|
||||||
buildOpts := cli.BuildOptions{
|
buildOpts := cli.BuildOptions{
|
||||||
Files: opts.files,
|
|
||||||
Profiles: opts.profiles,
|
|
||||||
Services: opts.services,
|
|
||||||
Push: true,
|
Push: true,
|
||||||
NoCache: false,
|
NoCache: false,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
"github.com/docker/docker/api/types/network"
|
"github.com/docker/docker/api/types/network"
|
||||||
dockerclient "github.com/docker/docker/client"
|
dockerclient "github.com/docker/docker/client"
|
||||||
|
"github.com/docker/go-connections/nat"
|
||||||
"github.com/psviderski/uncloud/internal/machine"
|
"github.com/psviderski/uncloud/internal/machine"
|
||||||
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
||||||
"github.com/psviderski/uncloud/internal/machine/cluster"
|
"github.com/psviderski/uncloud/internal/machine/cluster"
|
||||||
@@ -30,6 +31,8 @@ type Cluster struct {
|
|||||||
|
|
||||||
type CreateClusterOptions struct {
|
type CreateClusterOptions struct {
|
||||||
Machines int
|
Machines int
|
||||||
|
// Ports to forward from the cluster machines to the host.
|
||||||
|
PortMap nat.PortMap
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cluster) PopulateMachineIDs(ctx context.Context) error {
|
func (c *Cluster) PopulateMachineIDs(ctx context.Context) error {
|
||||||
@@ -82,6 +85,7 @@ func (p *Provisioner) CreateCluster(ctx context.Context, name string, opts Creat
|
|||||||
for i := 1; i < opts.Machines+1; i++ {
|
for i := 1; i < opts.Machines+1; i++ {
|
||||||
mopts := CreateMachineOptions{
|
mopts := CreateMachineOptions{
|
||||||
Name: fmt.Sprintf("machine-%d", i),
|
Name: fmt.Sprintf("machine-%d", i),
|
||||||
|
PortMap: opts.PortMap,
|
||||||
}
|
}
|
||||||
m, err := p.CreateMachine(ctx, name, mopts)
|
m, err := p.CreateMachine(ctx, name, mopts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ func (m *Machine) Connect(ctx context.Context) (*client.Client, error) {
|
|||||||
type CreateMachineOptions struct {
|
type CreateMachineOptions struct {
|
||||||
Name string
|
Name string
|
||||||
Image string
|
Image string
|
||||||
|
// Ports to forward from the machine to the host.
|
||||||
|
PortMap nat.PortMap
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provisioner) CreateMachine(ctx context.Context, clusterName string, opts CreateMachineOptions) (Machine, error) {
|
func (p *Provisioner) CreateMachine(ctx context.Context, clusterName string, opts CreateMachineOptions) (Machine, error) {
|
||||||
@@ -88,6 +90,16 @@ func (p *Provisioner) CreateMachine(ctx context.Context, clusterName string, opt
|
|||||||
Name: container.RestartPolicyAlways,
|
Name: container.RestartPolicyAlways,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
// Forward ports, if requested
|
||||||
|
if opts.PortMap != nil {
|
||||||
|
for port, bindings := range opts.PortMap {
|
||||||
|
if len(bindings) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
config.ExposedPorts[port] = struct{}{}
|
||||||
|
hostConfig.PortBindings[port] = bindings
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := p.createContainerWithImagePull(ctx, containerName, config, hostConfig); err != nil {
|
if _, err := p.createContainerWithImagePull(ctx, containerName, config, hostConfig); err != nil {
|
||||||
return m, err
|
return m, err
|
||||||
|
|||||||
@@ -0,0 +1,163 @@
|
|||||||
|
package e2e
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
composecli "github.com/compose-spec/compose-go/v2/cli"
|
||||||
|
"github.com/compose-spec/compose-go/v2/types"
|
||||||
|
"github.com/docker/docker/api/types/image"
|
||||||
|
dockerclient "github.com/docker/docker/client"
|
||||||
|
"github.com/docker/go-connections/nat"
|
||||||
|
"github.com/google/go-containerregistry/pkg/name"
|
||||||
|
"github.com/google/go-containerregistry/pkg/v1/remote"
|
||||||
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
|
cliInternal "github.com/psviderski/uncloud/internal/cli"
|
||||||
|
"github.com/psviderski/uncloud/internal/ucind"
|
||||||
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
|
"github.com/psviderski/uncloud/pkg/client/compose"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestComposeBuild(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
clusterName := "ucind-test.compose-build"
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
registryInternalPort := "5000/tcp"
|
||||||
|
clusterOpts := ucind.CreateClusterOptions{
|
||||||
|
Machines: 1,
|
||||||
|
PortMap: nat.PortMap{
|
||||||
|
// Forwarding the registry port to the host.
|
||||||
|
nat.Port(registryInternalPort): []nat.PortBinding{
|
||||||
|
{
|
||||||
|
HostIP: "127.0.0.1",
|
||||||
|
// Host port is a random available port.
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
c, _ := createTestCluster(t, clusterName, clusterOpts, true)
|
||||||
|
|
||||||
|
machine := c.Machines[0]
|
||||||
|
client, err := machine.Connect(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Get the (random) assigned host port for the future registry service
|
||||||
|
dockerCli, err := dockerclient.NewClientWithOpts(dockerclient.FromEnv, dockerclient.WithAPIVersionNegotiation())
|
||||||
|
require.NoError(t, err)
|
||||||
|
containerInfo, err := dockerCli.ContainerInspect(ctx, machine.ContainerName)
|
||||||
|
require.NoError(t, err)
|
||||||
|
portMap := containerInfo.NetworkSettings.Ports
|
||||||
|
registryHostPortStr := portMap[nat.Port(registryInternalPort)][0].HostPort
|
||||||
|
registryHostPort, err := strconv.Atoi(registryHostPortStr)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.True(t, registryHostPort > 0, "Expected host port to be a valid number")
|
||||||
|
|
||||||
|
t.Run("build and push images for a basic project", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
registryServiceName := "registry"
|
||||||
|
t.Cleanup(func() {
|
||||||
|
removeServices(t, client, registryServiceName)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Start registry
|
||||||
|
resp, err := client.RunService(ctx, api.ServiceSpec{
|
||||||
|
Name: registryServiceName,
|
||||||
|
Mode: api.ServiceModeReplicated,
|
||||||
|
Container: api.ContainerSpec{
|
||||||
|
Image: "registry:2",
|
||||||
|
},
|
||||||
|
Ports: []api.PortSpec{
|
||||||
|
{
|
||||||
|
ContainerPort: 5000,
|
||||||
|
PublishedPort: 5000,
|
||||||
|
Mode: api.PortModeHost,
|
||||||
|
Protocol: api.ProtocolTCP,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, resp.ID)
|
||||||
|
assert.Equal(t, "registry", resp.Name)
|
||||||
|
|
||||||
|
project, err := compose.LoadProject(
|
||||||
|
ctx,
|
||||||
|
[]string{"fixtures/compose-build-basic/compose.yaml"},
|
||||||
|
composecli.WithEnv([]string{
|
||||||
|
fmt.Sprintf("TEST_REGISTRY_PORT=%s", registryHostPortStr),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
servicesToBuild := cliInternal.GetServicesThatNeedBuild(project)
|
||||||
|
serviceImage1 := fmt.Sprintf("127.0.0.1:%d/service-first", registryHostPort)
|
||||||
|
serviceImage2 := fmt.Sprintf("127.0.0.1:%d/service-second:version2", registryHostPort)
|
||||||
|
t.Cleanup(func() {
|
||||||
|
// Remove the images after the test
|
||||||
|
removeOptions := image.RemoveOptions{Force: true, PruneChildren: true}
|
||||||
|
_, err := dockerCli.ImageRemove(ctx, serviceImage1, removeOptions)
|
||||||
|
assert.NoErrorf(t, err, "failed to remove image %s on test cleanup: %w", serviceImage1, err)
|
||||||
|
_, err = dockerCli.ImageRemove(ctx, serviceImage2, removeOptions)
|
||||||
|
assert.NoErrorf(t, err, "failed to remove image %s on test cleanup: %w", serviceImage2, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
servicesToBuildExpected := map[string]types.ServiceConfig{
|
||||||
|
"service-first": {
|
||||||
|
Name: "service-first",
|
||||||
|
Build: &types.BuildConfig{
|
||||||
|
Context: path.Join(project.WorkingDir, "service-first-dir"),
|
||||||
|
Dockerfile: "Dockerfile",
|
||||||
|
},
|
||||||
|
Image: serviceImage1,
|
||||||
|
Environment: types.NewMappingWithEquals([]string{}),
|
||||||
|
Networks: map[string]*types.ServiceNetworkConfig{
|
||||||
|
"default": nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"service-second": {
|
||||||
|
Name: "service-second",
|
||||||
|
Build: &types.BuildConfig{
|
||||||
|
Context: path.Join(project.WorkingDir, "service-second-dir"),
|
||||||
|
Dockerfile: "Dockerfile.alt",
|
||||||
|
},
|
||||||
|
Image: serviceImage2,
|
||||||
|
Environment: types.NewMappingWithEquals([]string{}),
|
||||||
|
Networks: map[string]*types.ServiceNetworkConfig{
|
||||||
|
"default": nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.Equal(t, servicesToBuildExpected, servicesToBuild)
|
||||||
|
|
||||||
|
// Build and push the images
|
||||||
|
buildOpts := cli.BuildOptions{
|
||||||
|
Push: true,
|
||||||
|
NoCache: false,
|
||||||
|
}
|
||||||
|
cli.BuildServices(context.Background(), servicesToBuild, buildOpts)
|
||||||
|
|
||||||
|
// Check the image of the first service
|
||||||
|
ref1, err := name.NewRepository(fmt.Sprintf("127.0.0.1:%d/service-first", registryHostPort))
|
||||||
|
require.NoError(t, err)
|
||||||
|
tags, err := remote.List(ref1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, tags, []string{"latest"}, "Tags for service service-first do not match")
|
||||||
|
|
||||||
|
// Check the image of the second service
|
||||||
|
ref2, err := name.NewRepository(fmt.Sprintf("127.0.0.1:%d/service-second", registryHostPort))
|
||||||
|
require.NoError(t, err)
|
||||||
|
tags, err = remote.List(ref2)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, tags, []string{"version2"}, "Tags for service service-second do not match")
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@ import (
|
|||||||
func TestComposeDeployment(t *testing.T) {
|
func TestComposeDeployment(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
clusterName := "ucind-test.compose"
|
clusterName := "ucind-test.compose-deploy"
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
c, _ := createTestCluster(t, clusterName, ucind.CreateClusterOptions{Machines: 3}, true)
|
c, _ := createTestCluster(t, clusterName, ucind.CreateClusterOptions{Machines: 3}, true)
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
FROM busybox:1.37.0-musl
|
|
||||||
|
|
||||||
ENV SERVICE_NAME=busybox-first
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
FROM busybox:1.37.0-musl
|
|
||||||
|
|
||||||
ENV SERVICE_NAME=busybox-second
|
|
||||||
@@ -2,13 +2,13 @@ services:
|
|||||||
service-no-build:
|
service-no-build:
|
||||||
image: portainer/pause:3.9
|
image: portainer/pause:3.9
|
||||||
|
|
||||||
busybox-first:
|
service-first:
|
||||||
image: localhost:5000/busybox-first
|
image: 127.0.0.1:${TEST_REGISTRY_PORT}/service-first
|
||||||
build:
|
build:
|
||||||
context: busybox-first/
|
context: service-first-dir/
|
||||||
|
|
||||||
busybox-second:
|
service-second:
|
||||||
image: localhost:5000/busybox-second
|
image: 127.0.0.1:${TEST_REGISTRY_PORT}/service-second:version2
|
||||||
build:
|
build:
|
||||||
context: busybox-second/
|
context: service-second-dir/
|
||||||
dockerfile: Dockerfile.alt
|
dockerfile: Dockerfile.alt
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
FROM portainer/pause:3.9
|
||||||
|
|
||||||
|
ENV SERVICE_NAME=service-first
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
FROM portainer/pause:3.9
|
||||||
|
|
||||||
|
ENV SERVICE_NAME=service-second
|
||||||
Reference in New Issue
Block a user