(w http.ResponseWriter, r *http.Request)
| 859 | } |
| 860 | |
| 861 | func (h *PatchingHandler) Trigger(w http.ResponseWriter, r *http.Request) { |
| 862 | var body struct { |
| 863 | HostID string `json:"host_id"` |
| 864 | PatchType string `json:"patch_type"` |
| 865 | PackageName string `json:"package_name"` |
| 866 | PackageNames []string `json:"package_names"` |
| 867 | DryRun bool `json:"dry_run"` |
| 868 | // PendingApproval creates the run in pending_validation status WITHOUT |
| 869 | // enqueuing any work to the host. It's the "submit for approval" path: |
| 870 | // a second approver later calls ApproveRun to actually execute. Works |
| 871 | // for any patch_type (including patch_all, which cannot dry-run). |
| 872 | PendingApproval bool `json:"pending_approval"` |
| 873 | ScheduleOverride string `json:"schedule_override"` // "immediate" to bypass policy delay |
| 874 | } |
| 875 | if err := decodeJSON(r, &body); err != nil { |
| 876 | JSON(w, http.StatusBadRequest, map[string]string{"error": "Invalid JSON"}) |
| 877 | return |
| 878 | } |
| 879 | if body.HostID == "" || !isValidPatchUUID(body.HostID) { |
| 880 | JSON(w, http.StatusBadRequest, map[string]string{"error": "Valid host_id is required"}) |
| 881 | return |
| 882 | } |
| 883 | if body.PatchType != "patch_all" && body.PatchType != "patch_package" { |
| 884 | JSON(w, http.StatusBadRequest, map[string]string{"error": "patch_type must be patch_all or patch_package"}) |
| 885 | return |
| 886 | } |
| 887 | if body.DryRun && body.PatchType != "patch_package" { |
| 888 | JSON(w, http.StatusBadRequest, map[string]string{"error": "dry_run is only supported for patch_package"}) |
| 889 | return |
| 890 | } |
| 891 | if body.DryRun && body.PendingApproval { |
| 892 | JSON(w, http.StatusBadRequest, map[string]string{"error": "dry_run and pending_approval are mutually exclusive"}) |
| 893 | return |
| 894 | } |
| 895 | |
| 896 | var pkgName *string |
| 897 | var pkgNames []string |
| 898 | if body.PatchType == "patch_package" { |
| 899 | if len(body.PackageNames) > 0 { |
| 900 | for _, n := range body.PackageNames { |
| 901 | if !isValidPackageName(n) { |
| 902 | JSON(w, http.StatusBadRequest, map[string]string{"error": "Every package_names entry must be a valid package name"}) |
| 903 | return |
| 904 | } |
| 905 | } |
| 906 | if len(body.PackageNames) > 100 { |
| 907 | JSON(w, http.StatusBadRequest, map[string]string{"error": "package_names limited to 100 packages per run"}) |
| 908 | return |
| 909 | } |
| 910 | pkgNames = body.PackageNames |
| 911 | } else if body.PackageName != "" && isValidPackageName(body.PackageName) { |
| 912 | pkgName = &body.PackageName |
| 913 | } else { |
| 914 | JSON(w, http.StatusBadRequest, map[string]string{"error": "Valid package_name or non-empty package_names is required for patch_package"}) |
| 915 | return |
| 916 | } |
| 917 | } |
| 918 |
nothing calls this directly
no test coverage detected