Skip to content
Open
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
13 changes: 13 additions & 0 deletions docs/release-notes/2_18_0_summary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
## Major Changes

### Table of Contents

- **[VTOrc `--cell` flag auto-applied on Vitess v24+](#vtorc-cell-flag)**

### <a id="vtorc-cell-flag"/>VTOrc `--cell` flag auto-applied on Vitess v24+</a>

VTOrc deployments now receive `--cell=<cell>` automatically when the configured image tag parses to Vitess v24 or newer. This is required for Vitess v25+ where `--cell` is a hard requirement ([vitessio/vitess#20048](https://github.com/vitessio/vitess/pull/20048)) and was introduced in v24 ([vitessio/vitess#19047](https://github.com/vitessio/vitess/pull/19047)).

The flag is **only** emitted when the version is parseable from the image tag (e.g. `vitess/lite:v24.0.0-mysql80`). Rolling tags such as `vitess/lite:mysql80` or `vitess/lite:latest`, or digest-only references, do not get the flag — pin a versioned tag, or set `cell` explicitly via `vitessOrchestrator.extraFlags` if you need it.

Pre-v24 users are unaffected.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.26.2

require (
github.com/ahmetb/gen-crd-api-reference-docs v0.3.0
github.com/blang/semver/v4 v4.0.0
github.com/google/uuid v1.6.0
github.com/planetscale/operator-sdk-libs v0.0.0-20220216002626-1af183733234
github.com/prometheus/client_golang v1.23.2
Expand Down Expand Up @@ -85,7 +86,6 @@ require (
github.com/aws/aws-sdk-go-v2/service/sts v1.41.10 // indirect
github.com/aws/smithy-go v1.24.3 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575 // indirect
Expand Down
46 changes: 46 additions & 0 deletions pkg/operator/vitess/version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
Copyright 2026 PlanetScale Inc.

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 vitess

import (
"strings"

"github.com/blang/semver/v4"
)

// MajorVersionFromImage parses the Vitess major version from a Docker image
// reference like "vitess/lite:v24.0.0-mysql80". Returns (major, true) when
// the image carries a SemVer-compatible tag and (0, false) otherwise
// (rolling tags such as "mysql80" or "latest", digests, or empty input).
func MajorVersionFromImage(image string) (int, bool) {
if image == "" {
return 0, false
}
// Drop digest portion if present (e.g. "repo:tag@sha256:...").
if at := strings.IndexByte(image, '@'); at >= 0 {
image = image[:at]
}
colon := strings.LastIndexByte(image, ':')
if colon < 0 {
return 0, false
}
v, err := semver.ParseTolerant(image[colon+1:])
if err != nil {
return 0, false
}
return int(v.Major), true
}
119 changes: 119 additions & 0 deletions pkg/operator/vitess/version_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
Copyright 2026 PlanetScale Inc.

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 vitess

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestMajorVersionFromImage(t *testing.T) {
tests := []struct {
name string
image string
want int
wantOK bool
}{
{
name: "clean v24 tag",
image: "vitess/lite:v24.0.0",
want: 24,
wantOK: true,
},
{
name: "v24 rc with mysql suffix",
image: "vitess/lite:v24.0.0-rc1-mysql80",
want: 24,
wantOK: true,
},
{
name: "v23 with mysql suffix",
image: "vitess/lite:v23.0.5-mysql80",
want: 23,
wantOK: true,
},
{
name: "large major version",
image: "vitess/lite:v100.2.3",
want: 100,
wantOK: true,
},
{
name: "tag and digest both present",
image: "vitess/lite:v24.0.0@sha256:abc",
want: 24,
wantOK: true,
},
{
name: "registry prefix with version",
image: "registry.example.com/vitess/lite:v25.0.0-mysql80",
want: 25,
wantOK: true,
},
{
name: "rolling mysql tag",
image: "vitess/lite:mysql80",
want: 0,
wantOK: false,
},
{
name: "latest tag",
image: "vitess/lite:latest",
want: 0,
wantOK: false,
},
{
name: "digest only",
image: "vitess/lite@sha256:abc123",
want: 0,
wantOK: false,
},
{
name: "no tag",
image: "vitess/lite",
want: 0,
wantOK: false,
},
{
name: "empty",
image: "",
want: 0,
wantOK: false,
},
{
name: "tag without v prefix still parses as semver",
image: "vitess/lite:24.0.0",
want: 24,
wantOK: true,
},
{
name: "tag with only major still parses (tolerant)",
image: "vitess/lite:v24",
want: 24,
wantOK: true,
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got, ok := MajorVersionFromImage(tc.image)
assert.Equal(t, tc.wantOK, ok)
assert.Equal(t, tc.want, got)
})
}
}
9 changes: 8 additions & 1 deletion pkg/operator/vtorc/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ func UpdateDeployment(obj *appsv1.Deployment, spec *Spec) {
}

func (spec *Spec) flags() vitess.Flags {
return vitess.Flags{
flags := vitess.Flags{
"topo_implementation": spec.GlobalLockserver.Implementation,
"topo_global_server_address": spec.GlobalLockserver.Address,
"topo_global_root": spec.GlobalLockserver.RootPath,
Expand All @@ -220,4 +220,11 @@ func (spec *Spec) flags() vitess.Flags {

"logtostderr": true,
}
// --cell was introduced in Vitess v24 and is required in v25+. Only emit
// it when we can confirm the image is on a supported version; on rolling
// or unparseable tags users can still force it via ExtraFlags.
if major, ok := vitess.MajorVersionFromImage(spec.Image); ok && major >= 24 {
flags["cell"] = spec.Cell
}
return flags
}
122 changes: 122 additions & 0 deletions pkg/operator/vtorc/deployment_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
Copyright 2026 PlanetScale Inc.

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 vtorc

import (
"testing"

"github.com/stretchr/testify/assert"

planetscalev2 "planetscale.dev/vitess-operator/pkg/apis/planetscale/v2"
)

func newSpecForTest(image, cell string) *Spec {
return &Spec{
GlobalLockserver: planetscalev2.VitessLockserverParams{
Implementation: "etcd2",
Address: "etcd:2379",
RootPath: "/vitess/global",
},
Keyspace: "commerce",
Shard: "-",
Cell: cell,
Image: image,
}
}

func TestSpecFlagsAlwaysIncludesBaseFlags(t *testing.T) {
spec := newSpecForTest("vitess/lite:v24.0.0-mysql80", "zone1")
flags := spec.flags()

for _, key := range []string{
"topo_implementation",
"topo_global_server_address",
"topo_global_root",
"port",
"clusters_to_watch",
"logtostderr",
} {
_, ok := flags[key]
assert.Truef(t, ok, "expected base flag %q to be present", key)
}

assert.Equal(t, "commerce/-", flags["clusters_to_watch"])
}

func TestSpecFlagsCellGatedByVitessVersion(t *testing.T) {
tests := []struct {
name string
image string
wantCell bool
}{
{
name: "v24 emits cell",
image: "vitess/lite:v24.0.0-mysql80",
wantCell: true,
},
{
name: "v25 emits cell",
image: "vitess/lite:v25.0.0-mysql80",
wantCell: true,
},
{
name: "v24 rc emits cell",
image: "vitess/lite:v24.0.0-rc1-mysql80",
wantCell: true,
},
{
name: "v23 does not emit cell",
image: "vitess/lite:v23.0.5-mysql80",
wantCell: false,
},
{
name: "v22 does not emit cell",
image: "vitess/lite:v22.0.0-mysql80",
wantCell: false,
},
{
name: "rolling mysql tag does not emit cell",
image: "vitess/lite:mysql80",
wantCell: false,
},
{
name: "latest tag does not emit cell",
image: "vitess/lite:latest",
wantCell: false,
},
{
name: "empty image does not emit cell",
image: "",
wantCell: false,
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
spec := newSpecForTest(tc.image, "zone1")
flags := spec.flags()

got, present := flags["cell"]
if tc.wantCell {
assert.True(t, present, "expected --cell flag to be present")
assert.Equal(t, "zone1", got)
} else {
assert.False(t, present, "expected --cell flag to be absent, got %v", got)
}
})
}
}
3 changes: 3 additions & 0 deletions test/endtoend/upgrade_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,9 @@ checkSemiSyncSetup
checkMysqldExporterMetrics
# Initially too durability policy should be specified
verifyDurabilityPolicy "commerce" "semi_sync"
# VTOrc --cell is gated on Vitess v24+ in the operator. The initial cluster
# pins v24.0.0-rc1, so the flag must be on the vtorc pod.
checkPodSpecBySelectorWithTimeout example "planetscale.com/component=vtorc" 1 "--cell=zone1"
upgradeToLatest
verifyVtGateVersion "25.0.0"
verifyResourceSpec
Expand Down