Take a module, converge a node after a preview, and return it to adopted, from the command line and the API (hq ADR 0100)

This commit is contained in:
2026-09-22 17:27:35 +02:00
parent 28894fa5bd
commit 1ea84f8b8f
6 changed files with 683 additions and 39 deletions
+25 -6
View File
@@ -94,19 +94,29 @@ func (n notYet) Who(*http.Request) (string, error) {
func commands(who Authenticator) http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("POST /assign", acting(who, func(ctx context.Context, open *stores, in request) (string, error) {
mux.HandleFunc("POST /assign", acting(who, true, func(ctx context.Context, open *stores, in request) (string, error) {
return assign(ctx, open, in.Node, in.Module)
}))
mux.HandleFunc("POST /unassign", acting(who, func(ctx context.Context, open *stores, in request) (string, error) {
mux.HandleFunc("POST /unassign", acting(who, true, func(ctx context.Context, open *stores, in request) (string, error) {
return unassign(ctx, open, in.Node, in.Module)
}))
// Adoption (novox/hq ADR 0100): the same acts as `take`, `converge` and `adopt`.
mux.HandleFunc("POST /take", acting(who, true, func(ctx context.Context, open *stores, in request) (string, error) {
return take(ctx, open, in.Node, in.Module)
}))
mux.HandleFunc("POST /converge", acting(who, false, func(ctx context.Context, open *stores, in request) (string, error) {
return converge(ctx, open, in.Node, in.Yes, in.Filter)
}))
mux.HandleFunc("POST /adopt", acting(who, false, func(ctx context.Context, open *stores, in request) (string, error) {
return adopt(ctx, open, in.Node)
}))
// Anything else is said plainly, because a command surface answering 404 to a verb somebody
// expected is indistinguishable from one that is down.
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
refuse(w, http.StatusNotFound, fmt.Errorf(
"%s %s is not something this mesh can be asked; it accepts POST /assign and "+
"POST /unassign", r.Method, r.URL.Path))
"%s %s is not something this mesh can be asked; it accepts POST /assign, "+
"POST /unassign, POST /take, POST /converge and POST /adopt", r.Method, r.URL.Path))
})
return mux
}
@@ -114,11 +124,16 @@ func commands(who Authenticator) http.Handler {
type request struct {
Node string `json:"node"`
Module string `json:"module"`
// Yes and Filter are converge's: do it rather than preview it, and which module loads the
// mesh's filter.
Yes bool `json:"yes,omitempty"`
Filter string `json:"filter,omitempty"`
}
// acting is the shape every route shares: authenticate, read, act, answer.
func acting(
who Authenticator,
needsModule bool,
do func(context.Context, *stores, request) (string, error),
) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
@@ -131,8 +146,12 @@ func acting(
refuse(w, http.StatusBadRequest, fmt.Errorf("this is not a request this understands: %w", err))
return
}
if in.Node == "" || in.Module == "" {
refuse(w, http.StatusBadRequest, errors.New(`both "node" and "module" are needed`))
if in.Node == "" || (needsModule && in.Module == "") {
if needsModule {
refuse(w, http.StatusBadRequest, errors.New(`both "node" and "module" are needed`))
} else {
refuse(w, http.StatusBadRequest, errors.New(`"node" is needed`))
}
return
}