@@ -38,8 +38,11 @@ const (
3838 UnversionedSearchAttribute = buildIdSearchAttributePrefixUnversioned
3939 UnversionedVersionId = "__unversioned__"
4040
41- // Prefixes, Delimeters and Keys
42- WorkerDeploymentVersionIdDelimiter = "."
41+ // WorkerDeploymentVersionIdDelimiterV31 will be deleted once we stop supporting v31 version string fields
42+ // in external and internal APIs. Until then, both delimiters are banned in deployment name. All
43+ // deprecated version string fields in APIs keep using the old delimiter. Workflow SA uses new delimiter.
44+ WorkerDeploymentVersionIdDelimiterV31 = "."
45+ WorkerDeploymentVersionIdDelimiter = ":"
4346 WorkerDeploymentVersionWorkflowIDPrefix = "temporal-sys-worker-deployment-version"
4447 WorkerDeploymentWorkflowIDPrefix = "temporal-sys-worker-deployment"
4548 WorkerDeploymentVersionWorkflowIDDelimeter = ":"
@@ -153,22 +156,46 @@ func BuildIdIfUsingVersioning(stamp *commonpb.WorkerVersionStamp) string {
153156
154157// DeploymentFromCapabilities returns the deployment if it is using versioning V3, otherwise nil.
155158// It returns the deployment from the `options` if present, otherwise, from `capabilities`,
156- func DeploymentFromCapabilities (capabilities * commonpb.WorkerVersionCapabilities , options * deploymentpb.WorkerDeploymentOptions ) * deploymentpb.Deployment {
157- if options .GetWorkerVersioningMode () == enumspb .WORKER_VERSIONING_MODE_VERSIONED &&
158- options .GetDeploymentName () != "" &&
159- options .GetBuildId () != "" {
160- return & deploymentpb.Deployment {
161- SeriesName : options .GetDeploymentName (),
162- BuildId : options .GetBuildId (),
159+ func DeploymentFromCapabilities (capabilities * commonpb.WorkerVersionCapabilities , options * deploymentpb.WorkerDeploymentOptions ) (* deploymentpb.Deployment , error ) {
160+ if options .GetWorkerVersioningMode () == enumspb .WORKER_VERSIONING_MODE_VERSIONED {
161+ d := options .GetDeploymentName ()
162+ b := options .GetBuildId ()
163+ if d == "" {
164+ return nil , serviceerror .NewInvalidArgumentf ("versioned worker must have deployment name" )
165+ }
166+ if b == "" {
167+ return nil , serviceerror .NewInvalidArgumentf ("versioned worker must have build id" )
168+ }
169+ if strings .Contains (d , WorkerDeploymentVersionIdDelimiter ) || strings .Contains (d , WorkerDeploymentVersionIdDelimiterV31 ) {
170+ // TODO: allow '.' once we get rid of v31 stuff
171+ return nil , serviceerror .NewInvalidArgumentf ("deployment name cannot contain '%s' or '%s'" , WorkerDeploymentVersionIdDelimiter , WorkerDeploymentVersionIdDelimiterV31 )
163172 }
173+ return & deploymentpb.Deployment {
174+ SeriesName : d ,
175+ BuildId : b ,
176+ }, nil
164177 }
165178 if capabilities .GetUseVersioning () && capabilities .GetDeploymentSeriesName () != "" && capabilities .GetBuildId () != "" {
166179 return & deploymentpb.Deployment {
167180 SeriesName : capabilities .GetDeploymentSeriesName (),
168181 BuildId : capabilities .GetBuildId (),
169- }
182+ }, nil
170183 }
171- return nil
184+ return nil , nil
185+ }
186+
187+ func DeploymentNameFromCapabilities (capabilities * commonpb.WorkerVersionCapabilities , options * deploymentpb.WorkerDeploymentOptions ) string {
188+ if d := options .GetDeploymentName (); d != "" {
189+ return d
190+ }
191+ return capabilities .GetDeploymentSeriesName ()
192+ }
193+
194+ func BuildIdFromCapabilities (capabilities * commonpb.WorkerVersionCapabilities , options * deploymentpb.WorkerDeploymentOptions ) string {
195+ if d := options .GetBuildId (); d != "" {
196+ return d
197+ }
198+ return capabilities .GetBuildId ()
172199}
173200
174201func DeploymentVersionFromOptions (options * deploymentpb.WorkerDeploymentOptions ) * deploymentspb.WorkerDeploymentVersion {
@@ -198,16 +225,6 @@ func DeploymentIfValid(d *deploymentpb.Deployment) *deploymentpb.Deployment {
198225 return nil
199226}
200227
201- // DeploymentToString is intended to be used for logs and metrics only. Theoretically, it can map
202- // different deployments to the string.
203- // DO NOT USE IN SERVER LOGIC.
204- func DeploymentToString (deployment * deploymentpb.Deployment ) string {
205- if deployment == nil {
206- return "UNVERSIONED"
207- }
208- return deployment .GetSeriesName () + ":" + deployment .GetBuildId ()
209- }
210-
211228// MakeDirectiveForWorkflowTask returns a versioning directive based on the following parameters:
212229// - inheritedBuildId: build ID inherited from a past/previous wf execution (for Child WF or CaN)
213230// - assignedBuildId: the build ID to which the WF is currently assigned (i.e. mutable state's AssginedBuildId)
@@ -394,7 +411,12 @@ func ValidateDeployment(deployment *deploymentpb.Deployment) error {
394411 return serviceerror .NewInvalidArgument ("deployment cannot be nil" )
395412 }
396413 if deployment .GetSeriesName () == "" {
397- return serviceerror .NewInvalidArgument ("deployment series name cannot be empty" )
414+ return serviceerror .NewInvalidArgument ("deployment name cannot be empty" )
415+ }
416+ // TODO: remove '.' restriction once the v31 version strings are completely cleaned from external and internal API
417+ if strings .Contains (deployment .GetSeriesName (), WorkerDeploymentVersionIdDelimiterV31 ) ||
418+ strings .Contains (deployment .GetSeriesName (), WorkerDeploymentVersionIdDelimiter ) {
419+ return serviceerror .NewInvalidArgumentf ("deployment name cannot contain '%s' or '%s'" , WorkerDeploymentVersionIdDelimiterV31 , WorkerDeploymentVersionIdDelimiter )
398420 }
399421 if deployment .GetBuildId () == "" {
400422 return serviceerror .NewInvalidArgument ("deployment build ID cannot be empty" )
@@ -417,13 +439,13 @@ func ValidateDeploymentVersion(version *deploymentspb.WorkerDeploymentVersion) e
417439 return nil
418440}
419441
420- // ValidateDeploymentVersionString returns error if the deployment version is nil or it has empty version
442+ // ValidateDeploymentVersionStringV31 returns error if the deployment version is nil or it has empty version
421443// or deployment name.
422- func ValidateDeploymentVersionString (version string ) (* deploymentspb.WorkerDeploymentVersion , error ) {
444+ func ValidateDeploymentVersionStringV31 (version string ) (* deploymentspb.WorkerDeploymentVersion , error ) {
423445 if version == "" {
424446 return nil , serviceerror .NewInvalidArgument ("version is required" )
425447 }
426- v , err := WorkerDeploymentVersionFromString (version )
448+ v , err := WorkerDeploymentVersionFromStringV31 (version )
427449 if err != nil {
428450 return nil , serviceerror .NewInvalidArgumentf ("invalid version string %q, expected format is \" <deployment_name>.<build_id>\" " , version )
429451 }
@@ -481,7 +503,7 @@ func ValidateVersioningOverride(override *workflowpb.VersioningOverride) error {
481503 if override .GetDeployment () != nil {
482504 return ValidateDeployment (override .GetDeployment ())
483505 } else if override .GetPinnedVersion () != "" {
484- _ , err := ValidateDeploymentVersionString (override .GetPinnedVersion ())
506+ _ , err := ValidateDeploymentVersionStringV31 (override .GetPinnedVersion ())
485507 return err
486508 } else {
487509 return serviceerror .NewInvalidArgument ("must provide deployment (deprecated) or pinned version if behavior is 'PINNED'" )
@@ -625,13 +647,13 @@ func AddV31VersioningInfoToV32(info *workflowpb.WorkflowExecutionVersioningInfo)
625647 //nolint:staticcheck // SA1019: worker versioning v0.31
626648 if info .Version == "" && info .DeploymentVersion != nil {
627649 //nolint:staticcheck // SA1019: worker versioning v0.31
628- info .Version = ExternalWorkerDeploymentVersionToString (info .DeploymentVersion )
650+ info .Version = ExternalWorkerDeploymentVersionToStringV31 (info .DeploymentVersion )
629651 }
630652 if t := info .VersionTransition ; t != nil {
631653 //nolint:staticcheck // SA1019: worker versioning v0.31
632654 if t .Version == "" {
633655 //nolint:staticcheck // SA1019: worker versioning v0.31
634- t .Version = ExternalWorkerDeploymentVersionToString (t .DeploymentVersion )
656+ t .Version = ExternalWorkerDeploymentVersionToStringV31 (t .DeploymentVersion )
635657 }
636658 }
637659 if o := info .VersioningOverride ; o != nil {
@@ -644,7 +666,7 @@ func AddV31VersioningInfoToV32(info *workflowpb.WorkflowExecutionVersioningInfo)
644666 //nolint:staticcheck // SA1019: worker versioning v0.31
645667 o .Behavior = enumspb .VERSIONING_BEHAVIOR_PINNED
646668 //nolint:staticcheck // SA1019: worker versioning v0.31
647- o .PinnedVersion = ExternalWorkerDeploymentVersionToString (o .GetPinned ().GetVersion ())
669+ o .PinnedVersion = ExternalWorkerDeploymentVersionToStringV31 (o .GetPinned ().GetVersion ())
648670 }
649671 }
650672 }
@@ -675,7 +697,7 @@ func ConvertOverrideToV32(override *workflowpb.VersioningOverride) *workflowpb.V
675697 //nolint:staticcheck // SA1019: worker versioning v0.31
676698 if override .GetPinnedVersion () != "" {
677699 //nolint:staticcheck // SA1019: worker versioning v0.31
678- ret .GetPinned ().Version = ExternalWorkerDeploymentVersionFromString (override .GetPinnedVersion ())
700+ ret .GetPinned ().Version = ExternalWorkerDeploymentVersionFromStringV31 (override .GetPinnedVersion ())
679701 } else {
680702 //nolint:staticcheck // SA1019: worker versioning v0.30
681703 ret .GetPinned ().Version = ExternalWorkerDeploymentVersionFromDeployment (override .GetDeployment ())
@@ -688,25 +710,32 @@ func ConvertOverrideToV32(override *workflowpb.VersioningOverride) *workflowpb.V
688710 return ret
689711}
690712
691- func WorkerDeploymentVersionToString (v * deploymentspb.WorkerDeploymentVersion ) string {
713+ func WorkerDeploymentVersionToStringV31 (v * deploymentspb.WorkerDeploymentVersion ) string {
692714 if v == nil {
693715 return "__unversioned__"
694716 }
695- return v .GetDeploymentName () + WorkerDeploymentVersionIdDelimiter + v .GetBuildId ()
717+ return v .GetDeploymentName () + WorkerDeploymentVersionIdDelimiterV31 + v .GetBuildId ()
696718}
697719
698720func ExternalWorkerDeploymentVersionToString (v * deploymentpb.WorkerDeploymentVersion ) string {
699721 if v == nil {
700- return "__unversioned__ "
722+ return ""
701723 }
702724 return v .GetDeploymentName () + WorkerDeploymentVersionIdDelimiter + v .GetBuildId ()
703725}
704726
705- func ExternalWorkerDeploymentVersionFromString (s string ) * deploymentpb.WorkerDeploymentVersion {
727+ func ExternalWorkerDeploymentVersionToStringV31 (v * deploymentpb.WorkerDeploymentVersion ) string {
728+ if v == nil {
729+ return "__unversioned__"
730+ }
731+ return v .GetDeploymentName () + WorkerDeploymentVersionIdDelimiterV31 + v .GetBuildId ()
732+ }
733+
734+ func ExternalWorkerDeploymentVersionFromStringV31 (s string ) * deploymentpb.WorkerDeploymentVersion {
706735 if s == "" { // unset ramp is no longer supported in v32, so all empty version strings will be treated as unversioned.
707736 s = UnversionedVersionId
708737 }
709- v , _ := WorkerDeploymentVersionFromString (s )
738+ v , _ := WorkerDeploymentVersionFromStringV31 (s )
710739 if v == nil {
711740 return nil
712741 }
@@ -716,13 +745,26 @@ func ExternalWorkerDeploymentVersionFromString(s string) *deploymentpb.WorkerDep
716745 }
717746}
718747
719- func WorkerDeploymentVersionFromString (s string ) (* deploymentspb.WorkerDeploymentVersion , error ) {
748+ func WorkerDeploymentVersionFromStringV31 (s string ) (* deploymentspb.WorkerDeploymentVersion , error ) {
720749 if s == "__unversioned__" {
721750 return nil , nil
722751 }
723- before , after , found := strings .Cut (s , WorkerDeploymentVersionIdDelimiter )
724- if ! found {
725- return nil , fmt .Errorf ("expected delimiter %s not found in version string %s" , WorkerDeploymentVersionIdDelimiter , s )
752+ before , after , found := strings .Cut (s , WorkerDeploymentVersionIdDelimiterV31 )
753+ // Also try parsing via the v32 delimiter in case user is using an old CLI/SDK but passing new version strings.
754+ before32 , after32 , found32 := strings .Cut (s , WorkerDeploymentVersionIdDelimiter )
755+ if ! found && ! found32 {
756+ return nil , fmt .Errorf ("expected delimiter '%s' or '%s' not found in version string %s" , WorkerDeploymentVersionIdDelimiter , WorkerDeploymentVersionIdDelimiterV31 , s )
757+ }
758+ if found && found32 && len (before32 ) < len (before ) {
759+ // choose the values based on the delimiter appeared first to ensure that deployment name does not contain any of the banned delimiters
760+ before = before32
761+ after = after32
762+ }
763+ if len (before ) == 0 {
764+ return nil , fmt .Errorf ("deployment name is empty in version string %s" , s )
765+ }
766+ if len (after ) == 0 {
767+ return nil , fmt .Errorf ("build id is empty in version string %s" , s )
726768 }
727769 return & deploymentspb.WorkerDeploymentVersion {
728770 DeploymentName : before ,
@@ -744,7 +786,7 @@ func GetDeploymentNameFromWorkflowID(workflowID string) string {
744786// GenerateVersionWorkflowID is a helper that generates a system accepted
745787// workflowID which are used in our Worker Deployment Version workflows
746788func GenerateVersionWorkflowID (deploymentName string , buildID string ) string {
747- versionString := WorkerDeploymentVersionToString ( & deploymentspb .WorkerDeploymentVersion {
789+ versionString := ExternalWorkerDeploymentVersionToString ( & deploymentpb .WorkerDeploymentVersion {
748790 DeploymentName : deploymentName ,
749791 BuildId : buildID ,
750792 })
0 commit comments