diff --git a/api/jobs.go b/api/jobs.go index f4b4fcaaa66..a5312a40d85 100644 --- a/api/jobs.go +++ b/api/jobs.go @@ -1645,21 +1645,34 @@ type JobStatusesRequest struct { } type TagVersionRequest struct { - JobID string - Version string - Tag *JobTaggedVersion + // JobID string + Version string + Description string + // Tag *JobTaggedVersion WriteRequest } func (j *Jobs) TagVersion(jobID string, version string, name string, description string, q *WriteOptions) (*WriteMeta, error) { + // If the version is not provided, get the "active" version of the job + if version == "" { + job, _, err := j.Info(jobID, nil) + if err != nil { + return nil, err + } + version = strconv.FormatUint(*job.Version, 10) + } var tagRequest = &TagVersionRequest{ - JobID: jobID, - Version: version, - Tag: &JobTaggedVersion{ - Name: name, - Description: description, - }, + // JobID: jobID, + Version: version, + Description: description, + // Tag: &JobTaggedVersion{ + // Description: description, + // }, } - return j.client.put("/v1/job/"+url.PathEscape(jobID)+"/versions/"+version+"/tag", tagRequest, nil, q) + return j.client.put("/v1/job/"+url.PathEscape(jobID)+"/versions/"+name+"/tag", tagRequest, nil, q) +} + +func (j *Jobs) UntagVersion(jobID string, name string, q *WriteOptions) (*WriteMeta, error) { + return j.client.delete("/v1/job/"+url.PathEscape(jobID)+"/versions/"+name+"/tag", nil, nil, q) } diff --git a/command/agent/job_endpoint.go b/command/agent/job_endpoint.go index 82e138ef580..2105ea3398c 100644 --- a/command/agent/job_endpoint.go +++ b/command/agent/job_endpoint.go @@ -119,8 +119,10 @@ func (s *HTTPServer) JobSpecificRequest(resp http.ResponseWriter, req *http.Requ jobID := strings.TrimSuffix(path, "/action") return s.jobRunAction(resp, req, jobID) case strings.HasSuffix(path, "/tag"): - jobID := strings.Split(path, "/")[0] - return s.jobTagVersion(resp, req, jobID) + parts := strings.Split(path, "/") + jobID := parts[0] + name := parts[2] // job//tag/ + return s.jobTagVersion(resp, req, jobID, name) default: return s.jobCRUD(resp, req, path) } @@ -404,34 +406,70 @@ func (s *HTTPServer) jobRunAction(resp http.ResponseWriter, req *http.Request, j return s.execStream(conn, &args) } -func (s *HTTPServer) jobTagVersion(resp http.ResponseWriter, req *http.Request, jobID string) (interface{}, error) { +func (s *HTTPServer) jobTagVersion(resp http.ResponseWriter, req *http.Request, jobID string, name string) (interface{}, error) { + s.logger.Debug("xxxxxxxxxjobTagVersion", "jobID", jobID, "name", name) + s.logger.Debug("ReqMethod", req.Method) switch req.Method { case http.MethodPut, http.MethodPost: - return s.jobVersionApplyTag(resp, req, jobID) + return s.jobVersionApplyTag(resp, req, jobID, name) case http.MethodDelete: - return s.jobVersionUnsetTag(resp, req, jobID) + return s.jobVersionUnsetTag(resp, req, jobID, name) default: return nil, CodedError(405, ErrInvalidMethod) } } -func (s *HTTPServer) jobVersionApplyTag(resp http.ResponseWriter, req *http.Request, jobID string) (interface{}, error) { +// TODO: this is a copy of the function in command/job_history.go; any way to import it here? +// parseVersion parses the version flag and returns the index, whether it +// was set and potentially an error during parsing. +func parseVersion(input string) (uint64, bool, error) { + if input == "" { + return 0, false, nil + } + + u, err := strconv.ParseUint(input, 10, 64) + return u, true, err +} + +func (s *HTTPServer) jobVersionApplyTag(resp http.ResponseWriter, req *http.Request, jobID string, name string) (interface{}, error) { var args api.TagVersionRequest if err := decodeBody(req, &args); err != nil { return nil, CodedError(400, err.Error()) } - rpcArgs, err := APIJobTagRequestToStructs(&args) + // rpcArgs, err := APIJobTagRequestToStructs(&args, jobID, name) + // if err != nil { + // return nil, err + // } + + // numericVersion := args.Version + // ^-- args.Version can be ""! How do we represent nil as a Uint64? + numericVersion, versionIncluded, err := parseVersion(args.Version) if err != nil { return nil, err } + var versionPointer *uint64 + + if versionIncluded { + versionPointer = &numericVersion + } + + rpcArgs := &structs.JobTagRequest{ + JobID: jobID, + Version: versionPointer, + Name: name, + Description: args.Description, + } + // parseWriteRequest overrides Namespace, Region and AuthToken // based on values from the original http request s.parseWriteRequest(req, &rpcArgs.WriteRequest) + s.logger.Debug("CONFIRMING NAMESPACE", rpcArgs.WriteRequest.Namespace) + var out structs.JobTagResponse if err := s.agent.RPC("Job.TagVersion", &rpcArgs, &out); err != nil { return nil, err @@ -439,22 +477,41 @@ func (s *HTTPServer) jobVersionApplyTag(resp http.ResponseWriter, req *http.Requ return out, nil } -func (s *HTTPServer) jobVersionUnsetTag(resp http.ResponseWriter, req *http.Request, jobID string) (interface{}, error) { - var args api.TagVersionRequest +func (s *HTTPServer) jobVersionUnsetTag(resp http.ResponseWriter, req *http.Request, jobID string, name string) (interface{}, error) { - if err := decodeBody(req, &args); err != nil { - return nil, CodedError(400, err.Error()) - } + s.logger.Debug("jobVersionUnsetTag", "jobID", jobID) + s.logger.Debug("jobVersionUnsetTag2", "req", req) + // s.logger.Debug("jobVersionUnsetTag3", "vers", version) - rpcArgs, err := APIJobTagRequestToStructs(&args) - if err != nil { - return nil, err + // var args api.TagVersionRequest + + // if err := decodeBody(req, &args); err != nil { + // return nil, CodedError(400, err.Error()) + // } + + // rpcArgs, err := APIJobTagRequestToStructs(&args) + // if err != nil { + // return nil, err + // } + + // versionNumber, err := strconv.ParseUint(version, 10, 64) + // s.logger.Debug("versionNumber", versionNumber) + // if err != nil { + // return nil, err + // } + + rpcArgs := &structs.JobUnsetTagRequest{ + JobID: jobID, + Name: name, } // parseWriteRequest overrides Namespace, Region and AuthToken // based on values from the original http request s.parseWriteRequest(req, &rpcArgs.WriteRequest) + s.logger.Debug("CONFIRMING NAMESPACE", rpcArgs.WriteRequest.Namespace) + s.logger.Debug("CONFIRMING QUERYOPTIONS NAMESPACE", rpcArgs.QueryOptions.Namespace) + // ^--- TODO: So we have Namespace here! Why is it not being passed to the RPC? var out structs.JobTagResponse if err := s.agent.RPC("Job.UntagVersion", &rpcArgs, &out); err != nil { return nil, err @@ -2219,20 +2276,27 @@ func ApiJobTaggedVersionToStructs(jobTaggedVersion *api.JobTaggedVersion) *struc } } -func APIJobTagRequestToStructs(jobTagRequest *api.TagVersionRequest) (*structs.JobTagRequest, error) { - if jobTagRequest == nil { - return nil, nil - } - versionNumber, err := strconv.ParseUint(jobTagRequest.Version, 10, 64) - if err != nil { - return nil, err - } - return &structs.JobTagRequest{ - JobID: jobTagRequest.JobID, - Version: versionNumber, - Tag: ApiJobTaggedVersionToStructs(jobTagRequest.Tag), - }, nil -} +// func APIJobTagRequestToStructs(jobTagRequest *api.TagVersionRequest, jobID string, name string) (*structs.JobTagRequest, error) { +// if jobTagRequest == nil { +// return nil, nil +// } +// // versionNumber, err := strconv.ParseUint(jobTagRequest.Version, 10, 64) +// // if err != nil { +// // return nil, err +// // } +// return &structs.JobTagRequest{ +// JobID: jobTagRequest.JobID, +// // JobID: jobID, +// // Version: versionNumber, +// Version: jobTagRequest.Version, +// Description: jobTagRequest.Description, +// // Tag: ApiJobTaggedVersionToStructs(jobTagRequest.Tag), +// // Tag: &structs.JobTaggedVersion{ +// // Name: name, +// // Description: jobTagRequest.Description, +// // }, +// }, nil +// } func ApiAffinityToStructs(a1 *api.Affinity) *structs.Affinity { return &structs.Affinity{ diff --git a/command/commands.go b/command/commands.go index e12805f122c..b9167b3c9b1 100644 --- a/command/commands.go +++ b/command/commands.go @@ -526,6 +526,11 @@ func Commands(metaPtr *Meta, agentUi cli.Ui) map[string]cli.CommandFactory { Meta: meta, }, nil }, + "job tag unset": func() (cli.Command, error) { + return &JobTagUnsetCommand{ + Meta: meta, + }, nil + }, "job validate": func() (cli.Command, error) { return &JobValidateCommand{ Meta: meta, diff --git a/command/job_tag.go b/command/job_tag.go index 3989cb9023d..0725c9cef3c 100644 --- a/command/job_tag.go +++ b/command/job_tag.go @@ -5,7 +5,6 @@ package command import ( "fmt" - "io" "strings" "github.com/posener/complete" @@ -13,10 +12,6 @@ import ( type JobTagCommand struct { Meta - - Stdin io.Reader - Stdout io.WriteCloser - Stderr io.WriteCloser } func (c *JobTagCommand) Help() string { @@ -32,8 +27,10 @@ Usage: nomad job tag [options] nomad job tag -version 3 -name "My Golden Version" + nomad job tag unset -verion 3 + The first of the above will tag the latest version of the job, while the second - will specifically tag version 3 of the job. + will specifically tag version 3 of the job. The last example will unset a tag. Tag Specific Options: @@ -102,7 +99,96 @@ func (c *JobTagCommand) Run(args []string) int { } if name == "" { - c.Ui.Error("A version name is required") + c.Ui.Error("A version tag name is required") + c.Ui.Error(commandErrorText(c)) + return 1 + } + + // Get the HTTP client + client, err := c.Meta.Client() + if err != nil { + c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err)) + return 1 + } + + // Check if the job exists + jobIDPrefix := strings.TrimSpace(job) + jobID, _, err := c.JobIDByPrefix(client, jobIDPrefix, nil) + if err != nil { + c.Ui.Error(err.Error()) + return 1 + } + + _, err = client.Jobs().TagVersion(jobID, versionStr, name, description, nil) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error tagging job version: %s", err)) + return 1 + } + + return 0 +} + +// #region unset + +type JobTagUnsetCommand struct { + Meta +} + +func (c *JobTagUnsetCommand) Help() string { + helpText := ` + TODO: Write help text + ` + return strings.TrimSpace(helpText) +} + +func (c *JobTagUnsetCommand) Synopsis() string { + return "Remove a tag from a job version." +} + +func (c *JobTagUnsetCommand) AutocompleteFlags() complete.Flags { + return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient), + complete.Flags{ + "-version": complete.PredictNothing, + }) +} + +func (c *JobTagUnsetCommand) AutocompleteArgs() complete.Predictor { + return complete.PredictNothing +} + +func (c *JobTagUnsetCommand) Name() string { return "job tag unset" } + +func (c *JobTagUnsetCommand) Run(args []string) int { + var name string + + flags := c.Meta.FlagSet(c.Name(), FlagSetClient) + flags.Usage = func() { c.Ui.Output(c.Help()) } + flags.StringVar(&name, "name", "", "") + + if err := flags.Parse(args); err != nil { + return 1 + } + + if len(flags.Args()) != 1 { + c.Ui.Error("This command takes one argument: ") + c.Ui.Error(commandErrorText(c)) + return 1 + } + + var job = flags.Args()[0] + + if job == "" { + c.Ui.Error( + "A job name is required", + ) + c.Ui.Error(commandErrorText(c)) + return 1 + } + + if name == "" { + c.Ui.Error( + "A version tag name is required", + ) c.Ui.Error(commandErrorText(c)) return 1 } @@ -122,7 +208,7 @@ func (c *JobTagCommand) Run(args []string) int { return 1 } - _, err = client.Jobs().TagVersion(jobID, versionStr, name, description, nil) // TODO: writeoptions nil??? + _, err = client.Jobs().UntagVersion(jobID, name, nil) if err != nil { c.Ui.Error(fmt.Sprintf("Error tagging job version: %s", err)) return 1 @@ -130,3 +216,5 @@ func (c *JobTagCommand) Run(args []string) int { return 0 } + +// #endregion unset diff --git a/helper/raftutil/msgtypes.go b/helper/raftutil/msgtypes.go index 79684e2a5f8..0aebe5f0ed7 100644 --- a/helper/raftutil/msgtypes.go +++ b/helper/raftutil/msgtypes.go @@ -66,6 +66,7 @@ var msgTypeNames = map[structs.MessageType]string{ structs.NodePoolUpsertRequestType: "NodePoolUpsertRequestType", structs.NodePoolDeleteRequestType: "NodePoolDeleteRequestType", structs.JobVersionTagRequestType: "JobVersionTagRequestType", + structs.JobVersionTagUnsetRequestType: "JobVersionTagUnsetRequestType", structs.NamespaceUpsertRequestType: "NamespaceUpsertRequestType", structs.NamespaceDeleteRequestType: "NamespaceDeleteRequestType", } diff --git a/nomad/fsm.go b/nomad/fsm.go index 450d786cf5b..6ccc979eb29 100644 --- a/nomad/fsm.go +++ b/nomad/fsm.go @@ -387,6 +387,8 @@ func (n *nomadFSM) Apply(log *raft.Log) interface{} { return n.applyACLBindingRulesDelete(buf[1:], log.Index) case structs.JobVersionTagRequestType: return n.applyJobVersionTag(buf[1:], log.Index) + case structs.JobVersionTagUnsetRequestType: + return n.applyJobVersionTagUnset(buf[1:], log.Index) } // Check enterprise only message types. @@ -1188,7 +1190,9 @@ func (n *nomadFSM) applyJobVersionTag(buf []byte, index uint64) interface{} { panic(fmt.Errorf("failed to decode request: %v", err)) } - if err := n.state.UpdateJobVersionTag(index, req.QueryOptions.Namespace, req.JobID, req.Version, req.Tag); err != nil { + n.logger.Debug("### applyJobVersionTag", "WriteRequestNamespace", req.WriteRequest.Namespace, "reqQueryOptionsNamespace", req.QueryOptions.Namespace, "reqJobID", req.JobID, "reqName", req.Name) + + if err := n.state.UpdateJobVersionTag(index, req.QueryOptions.Namespace, req.JobID, *req.Version, req.Name, req.Description); err != nil { n.logger.Error("UpdateJobVersionTag failed", "error", err) return err } @@ -1196,6 +1200,26 @@ func (n *nomadFSM) applyJobVersionTag(buf []byte, index uint64) interface{} { return nil } +// applyJobVersionTagUnset is used to remove a job version tag +func (n *nomadFSM) applyJobVersionTagUnset(buf []byte, index uint64) interface{} { + defer metrics.MeasureSince([]string{"nomad", "fsm", "apply_job_version_tag_unset"}, time.Now()) + var req structs.JobUnsetTagRequest + + if err := structs.Decode(buf, &req); err != nil { + panic(fmt.Errorf("failed to decode request: %v", err)) + } + + n.logger.Debug("### applyJobVersionTagUnset", "WriteRequestNamespace", req.WriteRequest.Namespace, "reqQueryOptionsNamespace", req.QueryOptions.Namespace, "reqJobID", req.JobID, "reqName", req.Name) + + // if err := n.state.UnsetJobVersionTag(index, req.WriteRequest.Namespace, req.JobID, req.Name); err != nil { + if err := n.state.UnsetJobVersionTag(index, "default", req.JobID, req.Name); err != nil { // TODO: hardcoded "default". This was the crux of my tuesday night woes. Why does my delete request not have a namespace? Does the buf look different for this request type? + n.logger.Error("UnsetJobVersionTag failed", "error", err) + return err + } + + return nil +} + // applyJobStability is used to set the stability of a job func (n *nomadFSM) applyJobStability(buf []byte, index uint64) interface{} { defer metrics.MeasureSince([]string{"nomad", "fsm", "apply_job_stability"}, time.Now()) diff --git a/nomad/job_endpoint.go b/nomad/job_endpoint.go index 53e0922ade8..d80c40166f5 100644 --- a/nomad/job_endpoint.go +++ b/nomad/job_endpoint.go @@ -2400,8 +2400,8 @@ func (j *Job) GetServiceRegistrations( } func (j *Job) TagVersion(args *structs.JobTagRequest, reply *structs.JobTagResponse) error { - - args.Tag.TaggedTime = time.Now().UnixNano() + // args.Tag.TaggedTime = time.Now().UnixNano() + // TODO: handle in state_store.go _, index, err := j.srv.raftApply(structs.JobVersionTagRequestType, args) if err != nil { @@ -2413,8 +2413,11 @@ func (j *Job) TagVersion(args *structs.JobTagRequest, reply *structs.JobTagRespo return nil } -func (j *Job) UntagVersion(args *structs.JobTagRequest, reply *structs.JobTagResponse) error { - _, index, err := j.srv.raftApply(structs.JobVersionTagRequestType, args) +func (j *Job) UntagVersion(args *structs.JobUnsetTagRequest, reply *structs.JobTagResponse) error { + // log + j.logger.Debug("++++++UntagVersion", "argsName", args.Name) + j.logger.Debug("++++++UntagVersion Namespace", "argsNamespace", args.WriteRequest.Namespace, "argsQONamespace", args.QueryOptions.Namespace) + _, index, err := j.srv.raftApply(structs.JobVersionTagUnsetRequestType, args) if err != nil { j.logger.Error("untagging version failed", "error", err) return err diff --git a/nomad/state/state_store.go b/nomad/state/state_store.go index 9da8348341a..4999c4b5dbf 100644 --- a/nomad/state/state_store.go +++ b/nomad/state/state_store.go @@ -4885,19 +4885,43 @@ func (s *StateStore) updateJobStabilityImpl(index uint64, namespace, jobID strin return s.upsertJobImpl(index, nil, copy, true, txn) } -func (s *StateStore) UpdateJobVersionTag(index uint64, namespace, jobID string, jobVersion uint64, tag *structs.JobTaggedVersion) error { +// func (s *StateStore) UpdateJobVersionTag(index uint64, namespace, jobID string, jobVersion uint64, tag *structs.JobTaggedVersion) error { +func (s *StateStore) UpdateJobVersionTag(index uint64, namespace, jobID string, jobVersion uint64, name string, description string) error { txn := s.db.WriteTxn(index) defer txn.Abort() - if err := s.updateJobVersionTagImpl(index, namespace, jobID, jobVersion, tag, txn); err != nil { + if err := s.updateJobVersionTagImpl(index, namespace, jobID, jobVersion, name, description, txn); err != nil { return err } return txn.Commit() } -func (s *StateStore) updateJobVersionTagImpl(index uint64, namespace, jobID string, jobVersion uint64, tag *structs.JobTaggedVersion, txn *txn) error { +// TODO: Tuesday Night: I need to stop throwing jobVersion around here, or at least let it be nil-able. name should instead be a top-level property +// Absence of tag, and presence of name (which I'll have to separate from the tag itself), means we need to delete the tag found on the version with that name. +// Presence of tag, and presence of version, means we need to update the tag found on the version with that tagname. +// Presence of tag (and name) and absence of version means we need to do a job lookup to get the latest. + +// Name, no version, no tag: delete the tag from the latest version. +// Name, no version, tag: update the tag on the latest version. +// NOPE NOT A THING: Name, version, no tag: delete the tag from a specific version. +// Name, version, tag: update the tag on a specific version. + +// So I care about: JobID, Name, Version, ??? + +// func (s *StateStore) updateJobVersionTagImpl(index uint64, namespace, jobID string, jobVersion uint64, tag *structs.JobTaggedVersion, txn *txn) error { +func (s *StateStore) updateJobVersionTagImpl(index uint64, namespace, jobID string, jobVersion uint64, name string, description string, txn *txn) error { ws := memdb.NewWatchSet() + s.logger.Debug("===== UPDATEJOBEVERSIONTAGIMPL =====") + s.logger.Debug("jobVersion", jobVersion) + s.logger.Debug("jobID", jobID) + s.logger.Debug("namespace", namespace) + + tag := &structs.JobTaggedVersion{ + Name: name, + Description: description, + TaggedTime: time.Now().UnixNano(), + } // Note: could use JobByIDAndVersion to get the specific version we want here, // but then we'd have to make a second lookup to make sure we're not applying a duplicate tag name @@ -4906,12 +4930,25 @@ func (s *StateStore) updateJobVersionTagImpl(index uint64, namespace, jobID stri return err } + // versionslength + s.logger.Debug("versionslength", len(versions)) + duplicateVersionName := false var job *structs.Job for _, version := range versions { // Allow for a tag to be updated (new description, for example) but otherwise don't allow a same-tagname to a different version. - if tag != nil && version.TaggedVersion != nil && version.TaggedVersion.Name == tag.Name && version.Version != jobVersion { + // if &jobVersion != nil { + // if version.Version == jobVersion { + // job = version + // } + // } else { + // if version.TaggedVersion != nil && version.TaggedVersion.Name == tag.Name { + // duplicateVersionName = true + // break + // } + // } + if version.TaggedVersion != nil && version.TaggedVersion.Name == tag.Name && version.Version != jobVersion { duplicateVersionName = true break } @@ -4921,11 +4958,11 @@ func (s *StateStore) updateJobVersionTagImpl(index uint64, namespace, jobID stri } if duplicateVersionName { - return fmt.Errorf("Tag %q already exists on a different version of job %q", tag.Name, jobID) + return fmt.Errorf("tag %q already exists on a different version of job %q", tag.Name, jobID) } if job == nil { - return fmt.Errorf("Job %q version %d not found", jobID, jobVersion) + return fmt.Errorf("job %q version %d not found", jobID, jobVersion) } copy := job.Copy() @@ -4934,6 +4971,60 @@ func (s *StateStore) updateJobVersionTagImpl(index uint64, namespace, jobID stri return s.upsertJobVersion(index, copy, txn) } +// unset +func (s *StateStore) UnsetJobVersionTag(index uint64, namespace, jobID string, name string) error { + s.logger.Debug("@@@ Namespace at UnsetJobVersionTag time", namespace) + txn := s.db.WriteTxn(index) + defer txn.Abort() + + if err := s.unsetJobVersionTagImpl(index, namespace, jobID, name, txn); err != nil { + return err + } + + return txn.Commit() +} + +func (s *StateStore) unsetJobVersionTagImpl(index uint64, namespace, jobID string, name string, txn *txn) error { + ws := memdb.NewWatchSet() + + s.logger.Debug("===== UNSETJOBEVERSIONTAGIMPL =====") + s.logger.Debug("name", name) + s.logger.Debug("jobID", jobID) + s.logger.Debug("namespace", namespace) + + versions, err := s.JobVersionsByID(ws, namespace, jobID) + if err != nil { + return err + } + + // versionslength + s.logger.Debug("versionslength", len(versions)) + + var job *structs.Job + + for _, version := range versions { + // Log the version we're looking at + s.logger.Debug("@@@ version", version.Version) + if version.TaggedVersion != nil { + s.logger.Debug("@@@ versionTagName", version.TaggedVersion.Name) + } + s.logger.Debug("@@@ name", name) + if version.TaggedVersion != nil && version.TaggedVersion.Name == name { + job = version + break + } + } + + if job == nil { + return fmt.Errorf("tag %q not found on job %q", name, jobID) + } + + copy := job.Copy() + copy.TaggedVersion = nil + copy.ModifyIndex = index + return s.upsertJobVersion(index, copy, txn) +} + // UpdateDeploymentPromotion is used to promote canaries in a deployment and // potentially make a evaluation func (s *StateStore) UpdateDeploymentPromotion(msgType structs.MessageType, index uint64, req *structs.ApplyDeploymentPromoteRequest) error { diff --git a/nomad/structs/structs.go b/nomad/structs/structs.go index d252259d510..735ba34a432 100644 --- a/nomad/structs/structs.go +++ b/nomad/structs/structs.go @@ -128,6 +128,7 @@ const ( NodePoolUpsertRequestType MessageType = 59 NodePoolDeleteRequestType MessageType = 60 JobVersionTagRequestType MessageType = 61 + JobVersionTagUnsetRequestType MessageType = 62 // Namespace types were moved from enterprise and therefore start at 64 NamespaceUpsertRequestType MessageType = 64 @@ -4529,13 +4530,23 @@ type JobTaggedVersion struct { } type JobTagRequest struct { - JobID string - Version uint64 - Tag *JobTaggedVersion + JobID string + Name string + // Version string + Description string + Version *uint64 + // Tag *JobTaggedVersion QueryOptions WriteRequest } +type JobUnsetTagRequest struct { + JobID string + Name string + QueryOptions + WriteRequest // TODO: do deletes get a WriteRequest? +} + type JobTagResponse struct { Name string Description string