Skip to content

Commit

Permalink
Add command for listing container images (#117)
Browse files Browse the repository at this point in the history
This commit adds the `homadctl containers list` command that will return a list
of all container images in use in the nomad cluster. Specifically it looks for jobs
using the docker driver and grabs the image from their configuration.

This is a precursor to creating a command to check for available image upgrades.

Signed-off-by: David Bond <davidsbond93@gmail.com>
  • Loading branch information
davidsbond authored Sep 30, 2022
1 parent c052423 commit 353ffd7
Show file tree
Hide file tree
Showing 6 changed files with 168 additions and 44 deletions.
3 changes: 2 additions & 1 deletion apps/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ go 1.19
require (
github.com/gorilla/handlers v1.5.1
github.com/gorilla/mux v1.8.0
github.com/hashicorp/nomad/api v0.0.0-20220909162634-8ff79d8a2da0
github.com/hashicorp/nomad/api v0.0.0-20220930123803-fb1f5ea2d981
github.com/spf13/cobra v1.5.0
golang.org/x/exp v0.0.0-20220929160808-de9c53c655b9
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4
gopkg.in/yaml.v3 v3.0.1
)
Expand Down
10 changes: 6 additions & 4 deletions apps/go.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
Expand All @@ -17,8 +17,8 @@ github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9n
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/nomad/api v0.0.0-20220909162634-8ff79d8a2da0 h1:gsbyjDOGAUIM9UJc0JknQ9XCr3m2AasG+eXoufoNgdA=
github.com/hashicorp/nomad/api v0.0.0-20220909162634-8ff79d8a2da0/go.mod h1:Z0U0rpbh4Qlkgqu3iRDcfJBA+r3FgoeD1BfigmZhfzM=
github.com/hashicorp/nomad/api v0.0.0-20220930123803-fb1f5ea2d981 h1:Kxz3mE0kP/ffUjRCvDCVNa8pjKEnZPKE4tDm5qnPWOY=
github.com/hashicorp/nomad/api v0.0.0-20220930123803-fb1f5ea2d981/go.mod h1:1dS8jZqAXhEreBcb26wpaV4Llk2cLO2sucuDKI+oTIs=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
Expand All @@ -32,12 +32,14 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shoenig/test v0.3.1 h1:dhGZztS6nQuvJ0o0RtUiQHaEO4hhArh/WmWwik3Ols0=
github.com/shoenig/test v0.4.0 h1:3X4xG/Chx7mzi0h71Sm6Vo38q0EYaQIBZpYFRcA1HVM=
github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU=
github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
golang.org/x/exp v0.0.0-20220929160808-de9c53c655b9 h1:lNtcVz/3bOstm7Vebox+5m3nLh/BYWnhmc3AhXOW6oI=
golang.org/x/exp v0.0.0-20220929160808-de9c53c655b9/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
63 changes: 63 additions & 0 deletions apps/homadctl/cmd/containers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package cmd

import (
"encoding/json"
"fmt"
"os"

"github.com/davidsbond/homad/apps/homadctl/internal/nomad"
"github.com/spf13/cobra"
)

// Containers is the root command for tasks that manage container images within the homelab.
func Containers() *cobra.Command {
cmd := &cobra.Command{
Use: "containers",
Aliases: []string{"container"},
Args: cobra.NoArgs,
Short: "Subcommands for managing containers running in the homelab",
}

cmd.AddCommand(
containersList(),
)

return cmd
}

func containersList() *cobra.Command {
var jsonOut bool

cmd := &cobra.Command{
Use: "list",
Aliases: []string{"ls"},
Args: cobra.NoArgs,
Short: "List all containers running in the homelab",
RunE: func(cmd *cobra.Command, args []string) error {
client, err := nomad.NewClient()
if err != nil {
return err
}

images, err := nomad.Images(cmd.Context(), client)
if err != nil {
return err
}

if jsonOut {
return json.NewEncoder(os.Stdout).Encode(images)
}

for _, image := range images {
fmt.Println(image)
}

return nil
},
}

flags := cmd.PersistentFlags()
flags.BoolVar(&jsonOut, "json", false, "Output containers in JSON format")

return cmd
}
39 changes: 2 additions & 37 deletions apps/homadctl/cmd/gc.go
Original file line number Diff line number Diff line change
@@ -1,57 +1,22 @@
package cmd

import (
"strings"

"github.com/davidsbond/homad/apps/homadctl/internal/nomad"
"github.com/hashicorp/nomad/api"
"github.com/spf13/cobra"
)

// GC returns a cobra command that triggers all periodic jobs in the maintenance namespace that have a suffix of
// "gc". This is how garbage-collection tasks are typically named.
// GC returns a cobra command that triggers all periodic garbage collection jobs.
func GC() *cobra.Command {
return &cobra.Command{
Use: "gc",
Short: "Trigger all garbage collection jobs available",
RunE: func(cmd *cobra.Command, args []string) error {
const namespace = "maintenance"

client, err := nomad.NewClient()
if err != nil {
return err
}

client.SetNamespace(namespace)
jobs, _, err := client.Jobs().List(&api.QueryOptions{})
switch {
case err != nil:
return err
case len(jobs) == 0:
return nil
}

jobIDs := make([]string, 0)
for _, job := range jobs {
if !strings.HasSuffix(job.Name, "gc") {
continue
}

jobIDs = append(jobIDs, job.ID)
}

if len(jobIDs) == 0 {
return nil
}

for _, jobID := range jobIDs {
_, _, err = client.Jobs().PeriodicForce(jobID, &api.WriteOptions{})
if err != nil {
return err
}
}

return nil
return nomad.GarbageCollect(cmd.Context(), client)
},
}
}
96 changes: 94 additions & 2 deletions apps/homadctl/internal/nomad/client.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,103 @@
// Package nomad provides functions for interacting with a nomad cluster.
package nomad

import "github.com/hashicorp/nomad/api"
import (
"context"
"net/http"
"strings"
"time"

"github.com/hashicorp/nomad/api"
"golang.org/x/exp/maps"
)

// NewClient returns a new nomad client configured to perform calls to the homelab.
func NewClient() (*api.Client, error) {
return api.NewClient(&api.Config{
Address: "https://homelab.dsb.dev",
Address: "https://homelab.dsb.dev",
Namespace: api.AllNamespacesNamespace,
HttpClient: &http.Client{
Timeout: time.Minute,
},
})
}

// GarbageCollect triggers all periodic jobs within the nomad cluster that are suffixed with "gc". This is the typical
// pattern for custom garbage collection jobs.
func GarbageCollect(ctx context.Context, client *api.Client) error {
jobs, _, err := client.Jobs().List(&api.QueryOptions{Namespace: api.AllNamespacesNamespace})
switch {
case err != nil:
return err
case len(jobs) == 0:
return nil
}

for _, job := range jobs {
if ctx.Err() != nil {
return ctx.Err()
}

if !strings.HasSuffix(job.Name, "gc") {
continue
}

_, _, err = client.Jobs().PeriodicForce(job.ID, &api.WriteOptions{Namespace: job.Namespace})
if err != nil {
return err
}
}

return nil
}

// Images returns a slice of all container image tags used by tasks within the nomad cluster.
func Images(ctx context.Context, client *api.Client) ([]string, error) {
images := make(map[string]struct{})

jobs, _, err := client.Jobs().List(&api.QueryOptions{Namespace: api.AllNamespacesNamespace})
if err != nil {
return nil, err
}

for _, job := range jobs {
if ctx.Err() != nil {
return nil, ctx.Err()
}

info, _, err := client.Jobs().Info(job.ID, &api.QueryOptions{Namespace: job.Namespace})
if err != nil {
return nil, err
}

for _, taskGroup := range info.TaskGroups {
if ctx.Err() != nil {
return nil, ctx.Err()
}

for _, task := range taskGroup.Tasks {
if ctx.Err() != nil {
return nil, ctx.Err()
}

if task.Driver != "docker" {
continue
}

imageInterface, ok := task.Config["image"]
if !ok {
continue
}

image, ok := imageInterface.(string)
if !ok {
continue
}

images[image] = struct{}{}
}
}
}

return maps.Keys(images), nil
}
1 change: 1 addition & 0 deletions apps/homadctl/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ func main() {

root.AddCommand(
cmd.GC(),
cmd.Containers(),
)

if err := root.ExecuteContext(ctx); err != nil {
Expand Down

0 comments on commit 353ffd7

Please sign in to comment.