Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package actuated-cli for openSUSE:Factory checked in at 2026-08-28 19:56:48 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/actuated-cli (Old) and /work/SRC/openSUSE:Factory/.actuated-cli.new.1265 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "actuated-cli" Fri Aug 28 19:56:48 2026 rev:7 rq:1374386 version:0.2.16 Changes: -------- --- /work/SRC/openSUSE:Factory/actuated-cli/actuated-cli.changes 2026-07-02 20:16:06.703195747 +0200 +++ /work/SRC/openSUSE:Factory/.actuated-cli.new.1265/actuated-cli.changes 2026-08-28 19:59:18.971006522 +0200 @@ -1,0 +2,8 @@ +Fri Aug 28 12:08:55 UTC 2026 - Johannes Kastl <[email protected]> + +- Update to version 0.2.16: + * Add verbose runner output + * Clarify profiling units + * Add profiling command + +------------------------------------------------------------------- Old: ---- actuated-cli-0.2.15.obscpio New: ---- actuated-cli-0.2.16.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ actuated-cli.spec ++++++ --- /var/tmp/diff_new_pack.kfq4XL/_old 2026-08-28 19:59:21.072079980 +0200 +++ /var/tmp/diff_new_pack.kfq4XL/_new 2026-08-28 19:59:21.075080084 +0200 @@ -17,7 +17,7 @@ Name: actuated-cli -Version: 0.2.15 +Version: 0.2.16 Release: 0 Summary: CLI for actuated License: MIT ++++++ _service ++++++ --- /var/tmp/diff_new_pack.kfq4XL/_old 2026-08-28 19:59:21.177083651 +0200 +++ /var/tmp/diff_new_pack.kfq4XL/_new 2026-08-28 19:59:21.188084035 +0200 @@ -3,7 +3,7 @@ <param name="url">https://github.com/self-actuated/actuated-cli.git</param> <param name="scm">git</param> <param name="exclude">.git</param> - <param name="revision">refs/tags/v0.2.15</param> + <param name="revision">refs/tags/v0.2.16</param> <param name="versionformat">@PARENT_TAG@</param> <param name="versionrewrite-pattern">v(.*)</param> <param name="changesgenerate">enable</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.kfq4XL/_old 2026-08-28 19:59:21.250086203 +0200 +++ /var/tmp/diff_new_pack.kfq4XL/_new 2026-08-28 19:59:21.264086693 +0200 @@ -3,6 +3,6 @@ <param name="url">https://github.com/self-actuated/actuated-cli</param> <param name="changesrevision">c182e025f791e6bdaa813ab3224e9eb7934ac760</param></service><service name="tar_scm"> <param name="url">https://github.com/self-actuated/actuated-cli.git</param> - <param name="changesrevision">069937852d18bdc6d22b9fd287fed848cab7da8e</param></service></servicedata> + <param name="changesrevision">97c8dfa564cc29c671b6ee7a61a77067f1080764</param></service></servicedata> (No newline at EOF) ++++++ actuated-cli-0.2.15.obscpio -> actuated-cli-0.2.16.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/actuated-cli-0.2.15/README.md new/actuated-cli-0.2.16/README.md --- old/actuated-cli-0.2.15/README.md 2026-07-02 12:57:25.000000000 +0200 +++ new/actuated-cli-0.2.16/README.md 2026-08-21 11:02:37.000000000 +0200 @@ -4,7 +4,7 @@ Most of the operations on the actuated dashboard are available as CLI commands. -In GitHub's terminology, which is carried over into actuated, the flag `--owner` and the term "OWNER" refer exclusively to a GitHub organization, and not your personal user account (which would be the "actor"). +In GitHub's terminology, which is carried over into actuated, the flag `--owner` and the term "OWNER" refer to a repository owner. This can be a GitHub organization or your personal account. ## Installation @@ -40,6 +40,23 @@ ```bash actuated-cli runners actuated-samples + +# Include the operating system reported by each agent +actuated-cli runners --verbose actuated-samples +``` + +## Profile completed jobs + +List recent profiling snapshots for an organisation or personal repository owner: + +```bash +actuated-cli profile OWNER +``` + +Inspect the full snapshot for a GitHub Actions job: + +```bash +actuated-cli profile OWNER --id JOB_ID ``` ## View SSH sessions available: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/actuated-cli-0.2.15/cmd/profile.go new/actuated-cli-0.2.16/cmd/profile.go --- old/actuated-cli-0.2.15/cmd/profile.go 1970-01-01 01:00:00.000000000 +0100 +++ new/actuated-cli-0.2.16/cmd/profile.go 2026-08-21 11:02:37.000000000 +0200 @@ -0,0 +1,333 @@ +package cmd + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "math" + "net/http" + "os" + "strconv" + "strings" + "time" + + "github.com/olekukonko/tablewriter" + "github.com/self-actuated/actuated-cli/pkg" + "github.com/spf13/cobra" +) + +func makeProfile() *cobra.Command { + cmd := &cobra.Command{ + Use: "profile OWNER", + Short: "View profiling snapshots for completed jobs", + Args: cobra.ExactArgs(1), + Example: ` # Show the 20 most recent profiling snapshots + actuated-cli profile openfaasltd + + # Show more snapshots or return the raw JSON + actuated-cli profile openfaasltd --limit 50 + actuated-cli profile openfaasltd --json + + # Inspect the full snapshot for a GitHub Actions job + actuated-cli profile openfaasltd --id 96357108346`, + RunE: runProfileE, + } + + cmd.Flags().IntP("limit", "l", 20, "Number of recent snapshots to show") + cmd.Flags().String("id", "", "GitHub Actions job ID for a detailed snapshot") + cmd.Flags().BoolP("json", "j", false, "Request output in JSON format") + + return cmd +} + +func runProfileE(cmd *cobra.Command, args []string) error { + owner := strings.TrimSpace(args[0]) + if owner == "" { + return fmt.Errorf("owner is required") + } + + pat, err := getPat(cmd) + if err != nil { + return err + } + if pat == "" { + return fmt.Errorf("pat is required") + } + + limit, err := cmd.Flags().GetInt("limit") + if err != nil { + return err + } + if limit < 1 { + return fmt.Errorf("limit must be greater than zero") + } + + jobID, err := cmd.Flags().GetString("id") + if err != nil { + return err + } + if jobID != "" { + if _, err := strconv.ParseInt(jobID, 10, 64); err != nil { + return fmt.Errorf("id must be a numeric GitHub Actions job ID") + } + } + + staff, err := cmd.Flags().GetBool("staff") + if err != nil { + return err + } + requestJSON, err := cmd.Flags().GetBool("json") + if err != nil { + return err + } + + client := pkg.NewClient(http.DefaultClient, os.Getenv("ACTUATED_URL")) + var body string + var status int + if jobID == "" { + body, status, err = client.ListProfiles(pat, owner, limit, staff) + } else { + body, status, err = client.GetProfile(pat, owner, jobID, staff) + } + if err != nil { + return err + } + if status == http.StatusUnauthorized { + return fmt.Errorf("GitHub token rejected, run \"actuated-cli auth\" and try again") + } + if status != http.StatusOK { + return fmt.Errorf("unexpected status code: %d, message: %s", status, body) + } + + if requestJSON { + return printProfileJSON(os.Stdout, []byte(body)) + } + + if jobID != "" { + var snapshot ProfileSnapshot + if err := json.Unmarshal([]byte(body), &snapshot); err != nil { + return err + } + printProfileDetails(os.Stdout, snapshot) + return nil + } + + var snapshots []ProfileSummary + if err := json.Unmarshal([]byte(body), &snapshots); err != nil { + return err + } + printProfiles(os.Stdout, snapshots) + + return nil +} + +func printProfileJSON(w io.Writer, body []byte) error { + var formatted bytes.Buffer + if err := json.Indent(&formatted, body, "", " "); err != nil { + return err + } + _, err := fmt.Fprintln(w, formatted.String()) + return err +} + +func printProfiles(w io.Writer, snapshots []ProfileSummary) { + table := newProfileTable(w) + table.SetHeader([]string{"AGE", "REPO", "JOB/WORKFLOW", "AGENT/VM", "RUNTIME", "VCPU", "LOAD 1/5", "RAM", "DISK"}) + + for _, snapshot := range snapshots { + table.Append([]string{ + profileAge(snapshot.CompletedAt), + snapshot.Repo, + snapshot.Job + "\n" + snapshot.Workflow, + profileAgent(snapshot.AgentName) + "\n" + valueOrNA(snapshot.Hostname), + profileRuntime(snapshot.StartedAt, snapshot.CompletedAt), + profileCPU(snapshot.TotalCPU, snapshot.ShareFactor), + profileLoads(snapshot.MaxLoadAvg1, snapshot.MaxLoadAvg5), + profileUsage(snapshot.TotalMemoryGB, snapshot.MinAvailableMemoryGB), + profileUsage(snapshot.DiskSpaceTotalGB, snapshot.DiskSpaceFreeGB), + }) + } + + table.Render() +} + +func printProfileDetails(w io.Writer, snapshot ProfileSnapshot) { + table := newProfileTable(w) + table.SetHeader([]string{"FIELD", "VALUE"}) + rows := [][]string{ + {"Job ID", snapshot.JobID}, + {"Repository", snapshot.Owner + "/" + snapshot.Repo}, + {"Job", snapshot.Job}, + {"Workflow", snapshot.Workflow}, + {"Agent", profileAgent(snapshot.AgentName)}, + {"VM", valueOrNA(snapshot.Hostname)}, + {"Runtime", profileRuntime(snapshot.StartedAt, snapshot.CompletedAt)}, + {"vCPU", profileCPU(snapshot.TotalCPU, snapshot.ShareFactor)}, + {"Load average 1/5/15m", joinProfileValues(snapshot.MaxLoadAvg1, snapshot.MaxLoadAvg5, snapshot.MaxLoadAvg15)}, + {"RAM total", formatProfileBytes(snapshot.TotalMemoryBytes)}, + {"RAM minimum available", formatProfileBytes(snapshot.MinAvailableMemoryBytes)}, + {"Disk total/free/used", joinProfileBytes(snapshot.DiskSpaceTotalBytes, snapshot.DiskSpaceFreeBytes, snapshot.DiskSpaceUsedBytes)}, + {"Disk read/write", joinProfileBytes(snapshot.DiskReadTotalBytes, snapshot.DiskWriteTotalBytes)}, + {"Network RX/TX", joinProfileBytes(snapshot.NetworkReadTotalBytes, snapshot.NetworkWriteTotalBytes)}, + } + for _, row := range rows { + table.Append(row) + } + table.Render() +} + +func newProfileTable(w io.Writer) *tablewriter.Table { + table := tablewriter.NewWriter(w) + table.SetBorders(tablewriter.Border{Left: true, Top: true, Right: true, Bottom: true}) + table.SetCenterSeparator("|") + table.SetColumnSeparator("|") + table.SetRowSeparator("-") + table.SetAutoWrapText(false) + table.SetAutoFormatHeaders(false) + return table +} + +func profileAge(completed *time.Time) string { + if completed == nil { + return "n/a" + } + age := time.Since(*completed) + if age < 0 { + age = 0 + } + return age.Round(time.Second).String() +} + +func profileAgent(agentName string) string { + if agentName == "" { + return "n/a" + } + return agentName +} + +func profileRuntime(startedAt, completedAt *time.Time) string { + if startedAt == nil || completedAt == nil { + return "n/a" + } + return completedAt.Sub(*startedAt).Round(time.Second).String() +} + +func profileCPU(totalCPU *int, shareFactor *float64) string { + if totalCPU == nil { + return "n/a" + } + cpu := float64(*totalCPU) + if shareFactor != nil && *shareFactor > 0 { + cpu *= *shareFactor + } + return strconv.FormatFloat(cpu, 'f', -1, 64) +} + +func profileLoads(load1, load5 *float64) string { + return joinProfileValues(load1, load5) +} + +func profileUsage(total, available *float64) string { + if total == nil || available == nil || *total <= 0 || + math.IsNaN(*total) || math.IsNaN(*available) || + math.IsInf(*total, 0) || math.IsInf(*available, 0) { + return "n/a" + } + used := *total - *available + percentage := used / *total * 100 + return fmt.Sprintf("%.2f/%.2fGB (%.0f%%)", used, *total, percentage) +} + +func valueOrNA(value string) string { + if value == "" { + return "n/a" + } + return value +} + +func joinProfileValues(values ...*float64) string { + parts := make([]string, len(values)) + for i, value := range values { + if value == nil { + parts[i] = "n/a" + } else { + parts[i] = strconv.FormatFloat(*value, 'f', -1, 64) + } + } + return strings.Join(parts, "/") +} + +func joinProfileBytes(values ...*float64) string { + parts := make([]string, len(values)) + for i, value := range values { + parts[i] = formatProfileBytes(value) + } + return strings.Join(parts, "/") +} + +func formatProfileBytes(value *float64) string { + if value == nil { + return "n/a" + } + units := []string{"B", "KB", "MB", "GB", "TB"} + size := *value + unit := 0 + for math.Abs(size) >= 1000 && unit < len(units)-1 { + size /= 1000 + unit++ + } + return fmt.Sprintf("%.2f%s", size, units[unit]) +} + +// ProfileSummary matches the controller's snapshots list response. Capacity +// fields are converted to decimal GB by the controller query. +type ProfileSummary struct { + JobID string `json:"job_id"` + Owner string `json:"owner"` + Repo string `json:"repo"` + Job string `json:"job"` + Workflow string `json:"workflow"` + AgentName string `json:"agent_name,omitempty"` + Hostname string `json:"hostname,omitempty"` + StartedAt *time.Time `json:"started_at,omitempty"` + CompletedAt *time.Time `json:"completed_at,omitempty"` + + TotalCPU *int `json:"total_cpu,omitempty"` + TotalMemoryGB *float64 `json:"total_memory,omitempty"` + MinAvailableMemoryGB *float64 `json:"min_memory_available_gb,omitempty"` + ShareFactor *float64 `json:"share_factor,omitempty"` + MaxLoadAvg1 *float64 `json:"max_load_avg1,omitempty"` + MaxLoadAvg5 *float64 `json:"max_load_avg5,omitempty"` + DiskSpaceTotalGB *float64 `json:"disk_space_total,omitempty"` + DiskSpaceFreeGB *float64 `json:"disk_space_free,omitempty"` +} + +// ProfileSnapshot matches the controller's detailed snapshot response. Byte +// counters are returned directly from the stored vmmeter snapshot. +type ProfileSnapshot struct { + JobID string `json:"job_id"` + Owner string `json:"owner"` + Repo string `json:"repo"` + Job string `json:"job"` + Workflow string `json:"workflow"` + AgentName string `json:"agent_name,omitempty"` + Hostname string `json:"hostname,omitempty"` + StartedAt *time.Time `json:"started_at,omitempty"` + CompletedAt *time.Time `json:"completed_at,omitempty"` + + TotalCPU *int `json:"total_cpu,omitempty"` + TotalMemoryBytes *float64 `json:"total_memory,omitempty"` + MinAvailableMemoryBytes *float64 `json:"min_memory_available_gb,omitempty"` + ShareFactor *float64 `json:"share_factor,omitempty"` + MaxLoadAvg1 *float64 `json:"max_load_avg1,omitempty"` + MaxLoadAvg5 *float64 `json:"max_load_avg5,omitempty"` + MaxLoadAvg15 *float64 `json:"max_load_avg15,omitempty"` + DiskSpaceTotalBytes *float64 `json:"disk_space_total,omitempty"` + DiskSpaceFreeBytes *float64 `json:"disk_space_free,omitempty"` + DiskSpaceUsedBytes *float64 `json:"disk_space_used,omitempty"` + DiskReadTotalBytes *float64 `json:"disk_read_total,omitempty"` + DiskWriteTotalBytes *float64 `json:"disk_write_total,omitempty"` + NetworkReadTotalBytes *float64 `json:"network_read_total,omitempty"` + NetworkWriteTotalBytes *float64 `json:"network_write_total,omitempty"` +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/actuated-cli-0.2.15/cmd/profile_test.go new/actuated-cli-0.2.16/cmd/profile_test.go --- old/actuated-cli-0.2.15/cmd/profile_test.go 1970-01-01 01:00:00.000000000 +0100 +++ new/actuated-cli-0.2.16/cmd/profile_test.go 2026-08-21 11:02:37.000000000 +0200 @@ -0,0 +1,35 @@ +package cmd + +import ( + "bytes" + "strings" + "testing" +) + +func TestProfileUsage(t *testing.T) { + total := 8.0 + available := 2.0 + if got, want := profileUsage(&total, &available), "6.00/8.00GB (75%)"; got != want { + t.Fatalf("want %q, got %q", want, got) + } + if got := profileUsage(nil, &available); got != "n/a" { + t.Fatalf("want n/a for unavailable total, got %q", got) + } +} + +func TestProfileDetailsFormatsRawMemoryBytes(t *testing.T) { + total := 12_260_000_000.0 + available := 9_920_000_000.0 + var output bytes.Buffer + + printProfileDetails(&output, ProfileSnapshot{ + TotalMemoryBytes: &total, + MinAvailableMemoryBytes: &available, + }) + + for _, want := range []string{"12.26GB", "9.92GB"} { + if !strings.Contains(output.String(), want) { + t.Fatalf("want output to contain %q, got:\n%s", want, output.String()) + } + } +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/actuated-cli-0.2.15/cmd/root.go new/actuated-cli-0.2.16/cmd/root.go --- old/actuated-cli-0.2.15/cmd/root.go 2026-07-02 12:57:25.000000000 +0200 +++ new/actuated-cli-0.2.16/cmd/root.go 2026-08-21 11:02:37.000000000 +0200 @@ -18,8 +18,8 @@ Long: `This CLI can be used to review and manage jobs, and the actuated agent installed on your servers. -The --owner flag or OWNER argument is a GitHub organization, i.e. for the path: -self-actuated/actuated-cli, the owner is "self-actuated" also known as an org. +The --owner flag or OWNER argument is a GitHub repository owner. This can be +an organisation such as "self-actuated", or your personal GitHub account. Run "actuated-cli auth" to authenticate with GitHub. @@ -52,6 +52,7 @@ root.AddCommand(makeJobs()) root.AddCommand(makeRepair()) root.AddCommand(makeIncreases()) + root.AddCommand(makeProfile()) root.AddCommand(makeRestart()) root.AddCommand(makeAgentLogs()) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/actuated-cli-0.2.15/cmd/runners.go new/actuated-cli-0.2.16/cmd/runners.go --- old/actuated-cli-0.2.15/cmd/runners.go 2026-07-02 12:57:25.000000000 +0200 +++ new/actuated-cli-0.2.16/cmd/runners.go 2026-08-21 11:02:37.000000000 +0200 @@ -22,6 +22,9 @@ # List runners for all customers actuated-cli runners --staff OWNER + # Include the operating system in the table + actuated-cli runners --verbose OWNER + # List runners in JSON format actuated-cli runners --json OWNER `, @@ -30,6 +33,7 @@ cmd.RunE = runRunnersE cmd.Flags().Bool("images", false, "Show the image being used for the rootfs and Kernel") + cmd.Flags().BoolP("verbose", "v", false, "Show additional runner details") cmd.Flags().BoolP("json", "j", false, "Request output in JSON format") return cmd @@ -56,6 +60,10 @@ if err != nil { return err } + verbose, err := cmd.Flags().GetBool("verbose") + if err != nil { + return err + } requestJson, err := cmd.Flags().GetBool("json") if err != nil { @@ -68,7 +76,7 @@ c := pkg.NewClient(http.DefaultClient, os.Getenv("ACTUATED_URL")) - res, status, err := c.ListRunners(pat, owner, staff, images, requestJson) + res, status, err := c.ListRunners(pat, owner, staff, images, verbose, requestJson) if err != nil { return err } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/actuated-cli-0.2.15/cmd/upgrade.go new/actuated-cli-0.2.16/cmd/upgrade.go --- old/actuated-cli-0.2.15/cmd/upgrade.go 2026-07-02 12:57:25.000000000 +0200 +++ new/actuated-cli-0.2.16/cmd/upgrade.go 2026-08-21 11:02:37.000000000 +0200 @@ -86,7 +86,7 @@ var upgradeHosts []Host if allHosts { includeImages := false - hosts, httpStatus, err := c.ListRunners(pat, owner, staff, includeImages, true) + hosts, httpStatus, err := c.ListRunners(pat, owner, staff, includeImages, false, true) if err != nil { return err } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/actuated-cli-0.2.15/pkg/client.go new/actuated-cli-0.2.16/pkg/client.go --- old/actuated-cli-0.2.15/pkg/client.go 2026-07-02 12:57:25.000000000 +0200 +++ new/actuated-cli-0.2.16/pkg/client.go 2026-08-21 11:02:37.000000000 +0200 @@ -24,6 +24,70 @@ } } +func (c *Client) ListProfiles(patStr, owner string, limit int, staff bool) (string, int, error) { + u, _ := url.Parse(c.baseURL) + u.Path = "/api/v1/snapshots" + q := u.Query() + q.Set("owners", owner) + q.Set("limit", fmt.Sprintf("%d", limit)) + if staff { + q.Set("staff", "1") + } + u.RawQuery = q.Encode() + + req, err := http.NewRequest(http.MethodGet, u.String(), nil) + if err != nil { + return "", http.StatusBadRequest, err + } + req.Header.Set("Accept", "application/json") + req.Header.Set("Authorization", "Bearer "+patStr) + + res, err := c.httpClient.Do(req) + if err != nil { + return "", http.StatusServiceUnavailable, err + } + defer res.Body.Close() + + body, err := io.ReadAll(res.Body) + if err != nil { + return "", res.StatusCode, err + } + + return string(body), res.StatusCode, nil +} + +func (c *Client) GetProfile(patStr, owner, jobID string, staff bool) (string, int, error) { + u, _ := url.Parse(c.baseURL) + u.Path = "/api/v1/snapshot" + q := u.Query() + q.Set("owner", owner) + q.Set("job", jobID) + if staff { + q.Set("staff", "1") + } + u.RawQuery = q.Encode() + + req, err := http.NewRequest(http.MethodGet, u.String(), nil) + if err != nil { + return "", http.StatusBadRequest, err + } + req.Header.Set("Accept", "application/json") + req.Header.Set("Authorization", "Bearer "+patStr) + + res, err := c.httpClient.Do(req) + if err != nil { + return "", http.StatusServiceUnavailable, err + } + defer res.Body.Close() + + body, err := io.ReadAll(res.Body) + if err != nil { + return "", res.StatusCode, err + } + + return string(body), res.StatusCode, nil +} + func (c *Client) ListJobs(patStr string, owner string, staff bool, json bool) (string, int, error) { u, _ := url.Parse(c.baseURL) @@ -133,7 +197,7 @@ return string(body), res.StatusCode, nil } -func (c *Client) ListRunners(patStr string, owner string, staff, images, json bool) (string, int, error) { +func (c *Client) ListRunners(patStr string, owner string, staff, images, verbose, json bool) (string, int, error) { u, _ := url.Parse(c.baseURL) u.Path = "/api/v1/runners" @@ -147,6 +211,10 @@ q.Set("images", "1") } + if verbose { + q.Set("verbose", "1") + } + if len(owner) > 0 { q.Set("owner", owner) log.Printf("Requesting runners for owner %s", owner) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/actuated-cli-0.2.15/pkg/client_profile_test.go new/actuated-cli-0.2.16/pkg/client_profile_test.go --- old/actuated-cli-0.2.15/pkg/client_profile_test.go 1970-01-01 01:00:00.000000000 +0100 +++ new/actuated-cli-0.2.16/pkg/client_profile_test.go 2026-08-21 11:02:37.000000000 +0200 @@ -0,0 +1,61 @@ +package pkg + +import ( + "io" + "net/http" + "net/http/httptest" + "testing" +) + +func TestListProfiles(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/api/v1/snapshots" { + t.Fatalf("want snapshots path, got %q", r.URL.Path) + } + if got := r.URL.Query().Get("owners"); got != "alexellis" { + t.Fatalf("want owner alexellis, got %q", got) + } + if got := r.URL.Query().Get("limit"); got != "50" { + t.Fatalf("want limit 50, got %q", got) + } + if got := r.Header.Get("Authorization"); got != "Bearer token" { + t.Fatalf("want bearer token, got %q", got) + } + io.WriteString(w, "[]") + })) + defer server.Close() + + client := NewClient(server.Client(), server.URL) + body, status, err := client.ListProfiles("token", "alexellis", 50, false) + if err != nil { + t.Fatal(err) + } + if status != http.StatusOK || body != "[]" { + t.Fatalf("want 200 and empty list, got %d and %q", status, body) + } +} + +func TestGetProfile(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/api/v1/snapshot" { + t.Fatalf("want snapshot path, got %q", r.URL.Path) + } + if got := r.URL.Query().Get("owner"); got != "openfaasltd" { + t.Fatalf("want owner openfaasltd, got %q", got) + } + if got := r.URL.Query().Get("job"); got != "123" { + t.Fatalf("want job 123, got %q", got) + } + io.WriteString(w, `{}`) + })) + defer server.Close() + + client := NewClient(server.Client(), server.URL) + _, status, err := client.GetProfile("token", "openfaasltd", "123", false) + if err != nil { + t.Fatal(err) + } + if status != http.StatusOK { + t.Fatalf("want 200, got %d", status) + } +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/actuated-cli-0.2.15/pkg/client_runners_test.go new/actuated-cli-0.2.16/pkg/client_runners_test.go --- old/actuated-cli-0.2.15/pkg/client_runners_test.go 1970-01-01 01:00:00.000000000 +0100 +++ new/actuated-cli-0.2.16/pkg/client_runners_test.go 2026-08-21 11:02:37.000000000 +0200 @@ -0,0 +1,36 @@ +package pkg + +import ( + "io" + "net/http" + "net/http/httptest" + "testing" +) + +func TestListRunnersRequestsVerboseOutput(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if got, want := r.URL.Path, "/api/v1/runners"; got != want { + t.Fatalf("want path %q, got %q", want, got) + } + if got, want := r.URL.Query().Get("owner"), "alexellis"; got != want { + t.Fatalf("want owner %q, got %q", want, got) + } + if got, want := r.URL.Query().Get("verbose"), "1"; got != want { + t.Fatalf("want verbose %q, got %q", want, got) + } + if got := r.Header.Get("Accept"); got != "" { + t.Fatalf("want plain-text Accept header, got %q", got) + } + io.WriteString(w, "table") + })) + defer server.Close() + + client := NewClient(server.Client(), server.URL) + body, status, err := client.ListRunners("token", "alexellis", false, false, true, false) + if err != nil { + t.Fatal(err) + } + if status != http.StatusOK || body != "table" { + t.Fatalf("want 200 and table, got %d and %q", status, body) + } +} ++++++ actuated-cli.obsinfo ++++++ --- /var/tmp/diff_new_pack.kfq4XL/_old 2026-08-28 19:59:22.001112460 +0200 +++ /var/tmp/diff_new_pack.kfq4XL/_new 2026-08-28 19:59:22.009112740 +0200 @@ -1,5 +1,5 @@ name: actuated-cli -version: 0.2.15 -mtime: 1782989845 -commit: 069937852d18bdc6d22b9fd287fed848cab7da8e +version: 0.2.16 +mtime: 1787302957 +commit: 97c8dfa564cc29c671b6ee7a61a77067f1080764 ++++++ vendor.tar.gz ++++++
