diff --git a/Dockerfile b/Dockerfile index 9b16b47..3399578 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,6 +4,10 @@ WORKDIR /app COPY go.mod go.sum main.go ./ +COPY pkg ./pkg + +COPY cmd ./cmd + RUN CGO_ENABLED=0 GOOS=linux go build -tags netgo -ldflags '-w' . FROM scratch diff --git a/cmd/list.go b/cmd/list.go new file mode 100644 index 0000000..e243213 --- /dev/null +++ b/cmd/list.go @@ -0,0 +1,48 @@ +package cmd + +import ( + "encoding/json" + "fmt" + "log" + + "github.com/asottile/dockerfile" + "github.com/shivjm/dockerfile-image-tags/pkg/images" + "github.com/shivjm/dockerfile-image-tags/pkg/input" + "github.com/spf13/cobra" +) + +var ( + listCmd = &cobra.Command{ + Use: "list", + Short: "List images and tags", + Long: "Print a list of the images and tags in the Dockerfile as a JSON array. Every `FROM` instruction in the file corresponds to an entry in the array, so the same image might appear multiple times, with the same or different tags. A special marker (default: `?`) will be returned if the tag is not specified.", + Args: cobra.MaximumNArgs(1), + Run: func(cmd *cobra.Command, args []string) { + file, err := input.GetInput(args) + + if err != nil { + log.Fatalf("Could not read Dockerfile: %s", err) + } + + parsed, err := dockerfile.ParseReader(file) + + if err != nil { + log.Fatalf("Could not parse Dockerfile: %s\n", err) + } + + allImages := images.GetImages(parsed, unknownMarker) + + val, err := json.Marshal(allImages) + + if err != nil { + log.Fatalf("Could not serialize images as JSON: %s\n", err) + } + + fmt.Println(string(val)) + }, + } +) + +func init() { + rootCmd.AddCommand(listCmd) +} diff --git a/cmd/query.go b/cmd/query.go new file mode 100644 index 0000000..3cb3682 --- /dev/null +++ b/cmd/query.go @@ -0,0 +1,65 @@ +package cmd + +import ( + "fmt" + "log" + "os" + + "github.com/asottile/dockerfile" + "github.com/shivjm/dockerfile-image-tags/pkg/images" + "github.com/shivjm/dockerfile-image-tags/pkg/input" + "github.com/spf13/cobra" +) + +var ( + occurrence int + + queryCmd = &cobra.Command{ + Use: "query", + Short: "Find single image tag", + Long: "Print the tag for a specific image if found, exit with an error otherwise. The image may appear multiple times in the Dockerfile, in which case `occurrence` (default: 1) specifies which instance to return the tag for.", + Args: cobra.RangeArgs(1, 2), + Run: func(cmd *cobra.Command, args []string) { + var file *os.File + var query string + + if len(args) == 2 { + f, err := input.GetInput(args[0:1]) + + if err != nil { + log.Fatalf("Could not read Dockerfile: %s", err) + } + + file = f + query = args[1] + } else { + f, _ := input.GetInput([]string{}) + + file = f + query = args[0] + } + + parsed, err := dockerfile.ParseReader(file) + + if err != nil { + log.Fatalf("Could not parse Dockerfile: %s\n", err) + } + + allImages := images.GetImages(parsed, unknownMarker) + + tag, err := images.GetSingleTag(allImages, query, occurrence) + + if err != nil { + log.Fatalf("Could not find image in Dockerfile: %s", query) + } + + fmt.Println(tag) + }, + } +) + +func init() { + queryCmd.Flags().IntVarP(&occurrence, "occurrence", "n", 1, "which occurrence of image to return tag for") + + rootCmd.AddCommand(queryCmd) +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..2a06418 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,22 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +var ( + unknownMarker string + + rootCmd = &cobra.Command{ + Use: "dockerfile-image-tags", + Short: "List or query images and tags used in a Dockerfile.", + } +) + +func Execute() error { + return rootCmd.Execute() +} + +func init() { + rootCmd.PersistentFlags().StringVarP(&unknownMarker, "unknown-marker", "m", "?", "string to use to indicate unknown tag") +} diff --git a/main.go b/main.go index 1c1b653..968f531 100644 --- a/main.go +++ b/main.go @@ -1,66 +1,9 @@ package main import ( - "encoding/json" - "fmt" - "log" - - "github.com/asottile/dockerfile" - "github.com/spf13/cobra" - - "github.com/shivjm/dockerfile-image-tags/pkg/images" - "github.com/shivjm/dockerfile-image-tags/pkg/input" + "github.com/shivjm/dockerfile-image-tags/cmd" ) - func main() { - var unknownMarker string - var query string - var occurrence int - - var rootCmd = &cobra.Command{ - Use: "dockerfile-image-tags", - Short: "List or query images & tags used in a Dockerfile.", - Args: cobra.MaximumNArgs(1), - Run: func(cmd *cobra.Command, args []string) { - file, err := input.GetInput(args) - - if err != nil { - log.Fatalf("Could not read Dockerfile: %s", err) - } - - parsed, err := dockerfile.ParseReader(file) - - if err != nil { - log.Fatalf("Could not parse Dockerfile: %s\n", err) - } - - allImages := images.GetImages(parsed, unknownMarker) - - val, err := json.Marshal(allImages) - - if err != nil { - log.Fatalf("Could not serialize images as JSON: %s\n", err) - } - - if query == "" { - fmt.Println(string(val)) - } else { - tag, err := images.GetSingleTag(allImages, query, occurrence) - - if err != nil { - log.Fatalf("Could not find image in Dockerfile: %s", query) - } - - fmt.Println(tag) - } - }, - } - rootCmd.Flags().StringVarP(&unknownMarker, "unknown-marker", "m", "?", "string to use to indicate unknown tags") - rootCmd.Flags().StringVarP(&query, "query", "q", "", "single image to return tag for (see `occurrence`)") - rootCmd.Flags().IntVarP(&occurrence, "occurrence", "n", 1, "which occurrence of image to return tag for") - - if err := rootCmd.Execute(); err != nil { - log.Fatal(err) - } + cmd.Execute() } diff --git a/main_test.go b/pkg/images/images_test.go similarity index 76% rename from main_test.go rename to pkg/images/images_test.go index 30872d1..1ce3f1a 100644 --- a/main_test.go +++ b/pkg/images/images_test.go @@ -1,16 +1,18 @@ -package main +package images import ( "testing" "github.com/asottile/dockerfile" "github.com/stretchr/testify/assert" +) - "github.com/shivjm/dockerfile-image-tags/pkg/images" +const ( + Dockerfile = "../../tests/Dockerfile.1" ) func TestParsing(t *testing.T) { - expected := []images.Image{ + expected := []Image{ {Name: "golang", Tag: "1.17.0-alpine"}, {Name: "common", Tag: " * "}, {Name: "common", Tag: "fixme"}, @@ -19,13 +21,13 @@ func TestParsing(t *testing.T) { {Name: "quay.io/argoproj/argocd", Tag: "$ARGOCD_VERSION"}, } - commands, err := dockerfile.ParseFile("tests/Dockerfile.1") + commands, err := dockerfile.ParseFile(Dockerfile) if err != nil { - t.Errorf("Could not open Dockerfile.1: %s", err) + t.Errorf("Could not open %s: %s", Dockerfile, err) } - tags := images.GetImages(commands, " * ") + tags := GetImages(commands, " * ") assert.Equal(t, expected, tags) } @@ -49,16 +51,16 @@ func TestQuery(t *testing.T) { {query: "common", occurrence: 2, match: true, tag: "fixme"}, } - commands, err := dockerfile.ParseFile("tests/Dockerfile.1") + commands, err := dockerfile.ParseFile(Dockerfile) if err != nil { - t.Errorf("Could not open Dockerfile.1: %s", err) + t.Errorf("Could not open %s: %s", Dockerfile, err) } - tags := images.GetImages(commands, "?") + tags := GetImages(commands, "?") for _, c := range cases { - result, err := images.GetSingleTag(tags, c.query, c.occurrence) + result, err := GetSingleTag(tags, c.query, c.occurrence) if c.match { assert.NoError(t, err, "must match %v", c.query)