66 "fmt"
77 "io"
88 "os"
9+ "strings"
910 "time"
1011
1112 "github.com/OctopusDeploy/cli/pkg/apiclient"
@@ -39,6 +40,7 @@ const (
3940 FlagChannel = "channel"
4041 FlagPackageVersionSpec = "package"
4142 FlagGitResourceRefSpec = "git-resource"
43+ FlagCustomField = "custom-field"
4244
4345 FlagVersion = "version"
4446 FlagAliasReleaseNumberLegacy = "releaseNumber" // alias for FlagVersion
@@ -124,6 +126,7 @@ type CreateFlags struct {
124126 IgnoreChannelRules * flag.Flag [bool ]
125127 PackageVersionSpec * flag.Flag [[]string ]
126128 GitResourceRefsSpec * flag.Flag [[]string ]
129+ CustomFields * flag.Flag [[]string ]
127130}
128131
129132func NewCreateFlags () * CreateFlags {
@@ -140,6 +143,7 @@ func NewCreateFlags() *CreateFlags {
140143 IgnoreChannelRules : flag .New [bool ](FlagIgnoreChannelRules , false ),
141144 PackageVersionSpec : flag .New [[]string ](FlagPackageVersionSpec , false ),
142145 GitResourceRefsSpec : flag .New [[]string ](FlagGitResourceRefSpec , false ),
146+ CustomFields : flag .New [[]string ](FlagCustomField , false ),
143147 }
144148}
145149
@@ -173,6 +177,7 @@ func NewCmdCreate(f factory.Factory) *cobra.Command {
173177 flags .BoolVarP (& createFlags .IgnoreChannelRules .Value , createFlags .IgnoreChannelRules .Name , "" , false , "Allow creation of a release where channel rules would otherwise prevent it." )
174178 flags .StringArrayVarP (& createFlags .PackageVersionSpec .Value , createFlags .PackageVersionSpec .Name , "" , []string {}, "Version specification for a specific package.\n Format as {package}:{version}, {step}:{version} or {package-ref-name}:{packageOrStep}:{version}\n You may specify this multiple times" )
175179 flags .StringArrayVarP (& createFlags .GitResourceRefsSpec .Value , createFlags .GitResourceRefsSpec .Name , "" , []string {}, "Git reference for a specific Git resource.\n Format as {step}:{git-ref}, {step}:{git-resource-name}:{git-ref}\n You may specify this multiple times" )
180+ flags .StringArrayVarP (& createFlags .CustomFields .Value , createFlags .CustomFields .Name , "" , []string {}, "Custom field value to set on the release.\n Format as {name}:{value}. You may specify multiple times" )
176181
177182 // we want the help text to display in the above order, rather than alphabetical
178183 flags .SortFlags = false
@@ -221,6 +226,24 @@ func createRun(cmd *cobra.Command, f factory.Factory, flags *CreateFlags) error
221226 GitResourceRefs : flags .GitResourceRefsSpec .Value ,
222227 }
223228
229+ if len (flags .CustomFields .Value ) > 0 {
230+ customFields := make (map [string ]string )
231+ for _ , raw := range flags .CustomFields .Value {
232+ // expect first ':' to split name and value; allow value to contain additional ':' characters
233+ parts := strings .SplitN (raw , ":" , 2 )
234+ if len (parts ) != 2 {
235+ return fmt .Errorf ("invalid custom-field value '%s'; expected format name:value" , raw )
236+ }
237+ name := strings .TrimSpace (parts [0 ])
238+ value := strings .TrimSpace (parts [1 ])
239+ if name == "" {
240+ return fmt .Errorf ("invalid custom-field value '%s'; field name cannot be empty" , raw )
241+ }
242+ customFields [name ] = value
243+ }
244+ options .CustomFields = customFields
245+ }
246+
224247 if flags .ReleaseNotesFile .Value != "" {
225248 fileContents , err := os .ReadFile (flags .ReleaseNotesFile .Value )
226249 if err != nil {
@@ -254,6 +277,11 @@ func createRun(cmd *cobra.Command, f factory.Factory, flags *CreateFlags) error
254277 resolvedFlags .ReleaseNotes .Value = options .ReleaseNotes
255278 resolvedFlags .IgnoreExisting .Value = options .IgnoreIfAlreadyExists
256279 resolvedFlags .IgnoreChannelRules .Value = options .IgnoreChannelRules
280+ if len (options .CustomFields ) > 0 {
281+ for k , v := range options .CustomFields {
282+ resolvedFlags .CustomFields .Value = append (resolvedFlags .CustomFields .Value , fmt .Sprintf ("%s: %s" , k , v ))
283+ }
284+ }
257285
258286 autoCmd := flag .GenerateAutomationCmd (constants .ExecutableName + " release create" ,
259287 resolvedFlags .Project ,
@@ -265,6 +293,7 @@ func createRun(cmd *cobra.Command, f factory.Factory, flags *CreateFlags) error
265293 resolvedFlags .IgnoreChannelRules ,
266294 resolvedFlags .PackageVersionSpec ,
267295 resolvedFlags .GitResourceRefsSpec ,
296+ resolvedFlags .CustomFields ,
268297 resolvedFlags .Version ,
269298 )
270299 cmd .Printf ("\n Automation Command: %s\n " , autoCmd )
@@ -588,9 +617,46 @@ func AskQuestions(octopus *octopusApiClient.Client, stdout io.Writer, asker ques
588617 return err
589618 }
590619 }
620+
621+ if len (selectedChannel .CustomFieldDefinitions ) > 0 {
622+ if options .CustomFields == nil { // ensure map initialised
623+ options .CustomFields = make (map [string ]string , len (selectedChannel .CustomFieldDefinitions ))
624+ }
625+ for _ , customFieldDefinition := range selectedChannel .CustomFieldDefinitions {
626+ // skip if already provided via automation
627+ if _ , exists := options .CustomFields [customFieldDefinition .FieldName ]; exists {
628+ continue
629+ }
630+
631+ customFieldValue , err := askCustomField (customFieldDefinition , asker )
632+ if err != nil {
633+ return err
634+ }
635+ options .CustomFields [customFieldDefinition .FieldName ] = customFieldValue
636+ }
637+ }
638+
591639 return nil
592640}
593641
642+ func askCustomField (customFieldDefinition channels.ChannelCustomFieldDefinition , asker question.Asker ) (string , error ) {
643+ msg := fmt .Sprint (customFieldDefinition .FieldName )
644+ helpText := customFieldDefinition .Description
645+ var answer string
646+
647+ validator := func (val interface {}) error {
648+ str , _ := val .(string )
649+ if strings .TrimSpace (str ) == "" {
650+ return fmt .Errorf ("%s is required" , customFieldDefinition .FieldName )
651+ }
652+ return nil
653+ }
654+ if err := asker (& survey.Input {Message : msg , Help : helpText }, & answer , survey .WithValidator (validator )); err != nil {
655+ return "" , err
656+ }
657+ return answer , nil
658+ }
659+
594660func askVersion (ask question.Asker , defaultVersion string ) (string , error ) {
595661 var result string
596662 if err := ask (& survey.Input {
0 commit comments