Skip to content

Commit

Permalink
Unset methods generally now I stare long into the namespace abyss
Browse files Browse the repository at this point in the history
  • Loading branch information
philrenaud committed Aug 28, 2024
1 parent 1f8960b commit 34b0d70
Show file tree
Hide file tree
Showing 9 changed files with 361 additions and 61 deletions.
33 changes: 23 additions & 10 deletions api/jobs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
122 changes: 93 additions & 29 deletions command/agent/job_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -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/<jobID>/tag/<name>
return s.jobTagVersion(resp, req, jobID, name)
default:
return s.jobCRUD(resp, req, path)
}
Expand Down Expand Up @@ -404,57 +406,112 @@ 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
}
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
Expand Down Expand Up @@ -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{
Expand Down
5 changes: 5 additions & 0 deletions command/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
104 changes: 96 additions & 8 deletions command/job_tag.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,13 @@ package command

import (
"fmt"
"io"
"strings"

"github.com/posener/complete"
)

type JobTagCommand struct {
Meta

Stdin io.Reader
Stdout io.WriteCloser
Stderr io.WriteCloser
}

func (c *JobTagCommand) Help() string {
Expand All @@ -32,8 +27,10 @@ Usage: nomad job tag [options] <jobname>
nomad job tag -version 3 -name "My Golden Version" <jobname>
nomad job tag unset -verion 3 <jobname>
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:
Expand Down Expand Up @@ -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: <job>")
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
}
Expand All @@ -122,11 +208,13 @@ 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
}

return 0
}

// #endregion unset
1 change: 1 addition & 0 deletions helper/raftutil/msgtypes.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 34b0d70

Please sign in to comment.