feat: look up latest caddy version tag when deploying caddy service

This commit is contained in:
Pavel Sviderski
2025-02-16 21:10:06 +10:00
parent 625a8baed3
commit d0e46186ed
5 changed files with 216 additions and 77 deletions
+62 -3
View File
@@ -1,18 +1,35 @@
package client
import (
"fmt"
"github.com/Masterminds/semver"
"github.com/distribution/reference"
"regexp"
"uncloud/internal/api"
"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1/remote"
)
const CaddyServiceName = "caddy"
const (
CaddyServiceName = "caddy"
// CaddyImage is the official Caddy Docker image on Docker Hub: https://hub.docker.com/_/caddy
CaddyImage = "caddy"
)
var caddyImageTagRegex = regexp.MustCompile(`^2\.\d+\.\d+$`)
// NewCaddyDeployment creates a new deployment for a Caddy reverse proxy service.
// The service is deployed in global mode to all machines in the cluster. If the image is not provided, the latest
// version of the official Caddy Docker image is used.
func (cli *Client) NewCaddyDeployment(image string) (*Deployment, error) {
latest, err := latestCaddyImage()
if err != nil {
return nil, fmt.Errorf("look up latest Caddy image: %w", err)
}
if image == "" {
// TODO: fetch the latest version tag from the Docker Hub registry.
image = "caddy:latest"
image = reference.FamiliarString(latest)
}
spec := api.ServiceSpec{
@@ -41,3 +58,45 @@ func (cli *Client) NewCaddyDeployment(image string) (*Deployment, error) {
return cli.NewDeployment(spec, &RollingStrategy{})
}
// latestCaddyImage returns the latest image of the official Caddy Docker image on Docker Hub.
// The latest image is determined by the latest version tag 2.x.x.
func latestCaddyImage() (reference.NamedTagged, error) {
repo, err := name.NewRepository(CaddyImage)
if err != nil {
return nil, fmt.Errorf("parse image: %w", err)
}
tags, err := remote.List(repo)
if err != nil {
return nil, fmt.Errorf("list image tags: %w", err)
}
// Default to the 'latest' tag but try to find the latest version tag 2.x.x.
latestTag := "latest"
var latestVersion *semver.Version
for _, t := range tags {
if !caddyImageTagRegex.MatchString(t) {
continue
}
v, err := semver.NewVersion(t)
if err != nil {
continue
}
if latestVersion == nil || v.GreaterThan(latestVersion) {
latestVersion = v
latestTag = t
}
}
image, err := reference.ParseDockerRef(CaddyImage)
if err != nil {
return nil, fmt.Errorf("parse image: %w", err)
}
imageWithTag, err := reference.WithTag(image, latestTag)
if err != nil {
return nil, fmt.Errorf("set image tag: %w", err)
}
return imageWithTag, nil
}
+67
View File
@@ -0,0 +1,67 @@
package client
import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
"uncloud/internal/api"
)
func TestClient_NewCaddyDeployment(t *testing.T) {
t.Parallel()
cli := &Client{}
t.Run("latest image from Docker Hub", func(t *testing.T) {
t.Parallel()
deploy, err := cli.NewCaddyDeployment("")
require.NoError(t, err)
assert.Equal(t, "caddy", deploy.Spec.Name)
assert.Equal(t, api.ServiceModeGlobal, deploy.Spec.Mode)
assert.Regexp(t, `^caddy:2\.\d+\.\d+$`, deploy.Spec.Container.Image)
expectedPorts := []api.PortSpec{
{
PublishedPort: 80,
ContainerPort: 80,
Protocol: api.ProtocolTCP,
Mode: api.PortModeHost,
},
{
PublishedPort: 443,
ContainerPort: 443,
Protocol: api.ProtocolTCP,
Mode: api.PortModeHost,
},
}
assert.Equal(t, expectedPorts, deploy.Spec.Ports)
})
t.Run("custom image", func(t *testing.T) {
t.Parallel()
image := "my-caddy:1.2.3"
deploy, err := cli.NewCaddyDeployment(image)
require.NoError(t, err)
assert.Equal(t, "caddy", deploy.Spec.Name)
assert.Equal(t, api.ServiceModeGlobal, deploy.Spec.Mode)
assert.Equal(t, image, deploy.Spec.Container.Image)
expectedPorts := []api.PortSpec{
{
PublishedPort: 80,
ContainerPort: 80,
Protocol: api.ProtocolTCP,
Mode: api.PortModeHost,
},
{
PublishedPort: 443,
ContainerPort: 443,
Protocol: api.ProtocolTCP,
Mode: api.PortModeHost,
},
}
assert.Equal(t, expectedPorts, deploy.Spec.Ports)
})
}