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 ++++++

Reply via email to