Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions server/internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ type DockerSwarm struct {
BridgeNetworksSubnetBits int `koanf:"bridge_networks_subnet_bits" json:"bridge_networks_subnet_bits,omitempty"`
DatabaseNetworksCIDR string `koanf:"database_networks_cidr" json:"database_networks_cidr,omitempty"`
DatabaseNetworksSubnetBits int `koanf:"database_networks_subnet_bits" json:"database_networks_subnet_bits,omitempty"`
// ManifestURL is the URL from which the version manifest is fetched.
// Defaults to the pgEdge CDN URL if not set.
ManifestURL string `koanf:"manifest_url" json:"manifest_url,omitempty"`
// ManifestPath points to a local manifest file that bypasses URL fetching
// entirely. Useful for air-gapped environments or testing.
ManifestPath string `koanf:"manifest_path" json:"manifest_path,omitempty"`
}

func (d DockerSwarm) validate() []error {
Expand All @@ -102,6 +108,10 @@ func (d DockerSwarm) validate() []error {
return errs
}

// DefaultManifestURL is the pgEdge CDN URL used when no manifest_url is configured.
// TODO(PLAT-598): Replace with the real URL once the hosting location is confirmed.
const DefaultManifestURL = "https://download.pgedge.com/manifests/version-manifest.json"

var defaultDockerSwarm = DockerSwarm{
ImageRepositoryHost: "ghcr.io/pgedge",
// This combination gives us 256 subnets with 16 addresses each.
Expand All @@ -110,6 +120,7 @@ var defaultDockerSwarm = DockerSwarm{
// This combination gives us 256 subnets with 64 addresses each.
DatabaseNetworksCIDR: "10.128.128.0/18",
DatabaseNetworksSubnetBits: 26,
ManifestURL: DefaultManifestURL,
}

type SystemD struct {
Expand Down
5 changes: 5 additions & 0 deletions server/internal/database/orchestrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,4 +181,9 @@ type Orchestrator interface {
StartInstance(ctx context.Context, instanceID string) error
NodeDSN(ctx context.Context, rc *resource.Context, nodeName string, fromInstanceID string, dbName string) (*postgres.DSN, error)
InstancePaths(pgVersion *ds.Version, instanceID string) (InstancePaths, error)
// ReconcileInstanceSpec is called during spec reconciliation to allow the
// orchestrator to update computed fields (e.g. resolved image) on the new
// spec before it is persisted. old is nil when the instance is being created
// for the first time.
ReconcileInstanceSpec(old, new *InstanceSpec) error
}
6 changes: 6 additions & 0 deletions server/internal/database/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -669,7 +669,13 @@ func (s *Service) ReconcileInstanceSpec(ctx context.Context, spec *InstanceSpec)
case err == nil:
previous = stored.Spec
spec.CopySettingsFrom(previous)
if err := s.orchestrator.ReconcileInstanceSpec(previous, spec); err != nil {
return nil, fmt.Errorf("failed to reconcile instance spec: %w", err)
}
case errors.Is(err, storage.ErrNotFound):
if err := s.orchestrator.ReconcileInstanceSpec(nil, spec); err != nil {
return nil, fmt.Errorf("failed to reconcile instance spec: %w", err)
}
stored = &StoredInstanceSpec{}
default:
return nil, fmt.Errorf("failed to get current spec for instance '%s': %w", spec.InstanceID, err)
Expand Down
9 changes: 8 additions & 1 deletion server/internal/database/spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,12 @@ type ExtraNetworkSpec struct {
type SwarmOpts struct {
ExtraVolumes []ExtraVolumesSpec `json:"extra_volumes,omitempty"`
ExtraNetworks []ExtraNetworkSpec `json:"extra_networks,omitempty"`
ExtraLabels map[string]string `json:"extra_labels,omitempty"` // optional, used for custom labels on the swarm service
ExtraLabels map[string]string `json:"extra_labels,omitempty"`
// Image is a user-specified override. Never written by the CP.
Image string `json:"image,omitempty"`
// ResolvedImage is the CP-managed image tag. Written at instance creation,
// upgrade application, and lazy backfill. Never set simultaneously with Image.
ResolvedImage string `json:"resolved_image,omitempty"`
}
type OrchestratorOpts struct {
Swarm *SwarmOpts `json:"docker,omitempty"`
Expand Down Expand Up @@ -301,6 +306,8 @@ func (d *SwarmOpts) Clone() *SwarmOpts {
ExtraVolumes: clonedVolumes,
ExtraNetworks: clonedNetworks,
ExtraLabels: maps.Clone(d.ExtraLabels),
Image: d.Image,
ResolvedImage: d.ResolvedImage,
}
}

Expand Down
32 changes: 32 additions & 0 deletions server/internal/database/spec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,38 @@ func TestSpec(t *testing.T) {
})
}

func TestSwarmOptsClone(t *testing.T) {
t.Run("copies Image and ResolvedImage", func(t *testing.T) {
orig := &database.SwarmOpts{
Image: "custom-registry/pgedge:dev",
ResolvedImage: "registry/pgedge:17.9-spock5.0.6-standard-1",
ExtraLabels: map[string]string{"k": "v"},
}
cloned := orig.Clone()

assert.Equal(t, orig.Image, cloned.Image)
assert.Equal(t, orig.ResolvedImage, cloned.ResolvedImage)
})

t.Run("clone is independent of original", func(t *testing.T) {
orig := &database.SwarmOpts{
Image: "original-image",
ResolvedImage: "original-resolved",
}
cloned := orig.Clone()
cloned.Image = "mutated-image"
cloned.ResolvedImage = "mutated-resolved"

assert.Equal(t, "original-image", orig.Image)
assert.Equal(t, "original-resolved", orig.ResolvedImage)
})

t.Run("nil clone returns nil", func(t *testing.T) {
var s *database.SwarmOpts
assert.Nil(t, s.Clone())
})
}

func TestSpec_NodeInstances_DBOwner(t *testing.T) {
minimalSpec := func(users []*database.User) *database.Spec {
return &database.Spec{
Expand Down
1 change: 1 addition & 0 deletions server/internal/logging/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const (
ComponentDatabaseService Component = "database_service"
ComponentElectionCandidate Component = "election_candidate"
ComponentEmbeddedEtcd Component = "embedded_etcd"
ComponentManifestLoader Component = "manifest_loader"
ComponentMigration Component = "migration"
ComponentMigrationRunner Component = "migration_runner"
ComponentPortsService Component = "ports_service"
Expand Down
6 changes: 3 additions & 3 deletions server/internal/orchestrator/swarm/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,11 @@ func NewVersions(cfg config.Config) *Versions {
return versions
}

func (v *Versions) Supported() []*ds.PgEdgeVersion {
func (v Versions) Supported() []*ds.PgEdgeVersion {
return v.supportedVersions
}

func (v *Versions) Default() *ds.PgEdgeVersion {
func (v Versions) Default() *ds.PgEdgeVersion {
return v.defaultVersion
}

Expand All @@ -100,7 +100,7 @@ func (v *Versions) addImage(version *ds.PgEdgeVersion, images *Images) {
v.supportedVersions = append(v.supportedVersions, version)
}

func (v *Versions) GetImages(version *ds.PgEdgeVersion) (*Images, error) {
func (v Versions) GetImages(version *ds.PgEdgeVersion) (*Images, error) {
pgv := version.PostgresVersion.String()
sv := version.SpockVersion.String()

Expand Down
Loading