diff --git a/cmd/check.go b/cmd/check.go
index 5b15544..cf57dc9 100644
--- a/cmd/check.go
+++ b/cmd/check.go
@@ -17,12 +17,14 @@ limitations under the License.
package cmd
import (
- log "github.com/sirupsen/logrus"
"os"
"path"
+ log "github.com/sirupsen/logrus"
+
"github.com/spf13/cobra"
+ "sothr.com/warricksothr/pinned-package-updater/internal/parser"
"sothr.com/warricksothr/pinned-package-updater/internal/parser/dockerfile"
)
@@ -69,6 +71,14 @@ pinned-package-updater --remote
check [Dockerf
func init() {
rootCmd.AddCommand(checkCmd)
+ // TODO: Add dynamic loading of maps between images and command parsers
+
+ err := parser.InitCommandParsers()
+ if err != nil {
+ log.Errorf("Unexpected error while ")
+ return
+ }
+
// Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command
diff --git a/go.mod b/go.mod
index 23ca992..409d246 100644
--- a/go.mod
+++ b/go.mod
@@ -8,6 +8,7 @@ require (
github.com/spf13/cobra v1.2.1
github.com/spf13/viper v1.8.1
github.com/thediveo/enumflag v0.10.1
+ mvdan.cc/sh/v3 v3.4.0
)
require (
@@ -30,7 +31,7 @@ require (
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.2.0 // indirect
- golang.org/x/sys v0.0.0-20210510120138-977fb7262007 // indirect
+ golang.org/x/sys v0.0.0-20210925032602-92d5a993a665 // indirect
golang.org/x/text v0.3.5 // indirect
google.golang.org/protobuf v1.26.0 // indirect
gopkg.in/ini.v1 v1.62.0 // indirect
diff --git a/go.sum b/go.sum
index dbf1502..e27d858 100644
--- a/go.sum
+++ b/go.sum
@@ -338,6 +338,7 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsr
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/creack/pty v1.1.15/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ=
github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s=
@@ -414,6 +415,8 @@ github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoD
github.com/fortytw2/leaktest v1.2.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
+github.com/frankban/quicktest v1.13.1 h1:xVm/f9seEhZFL9+n5kv5XLrGwy6elc4V9v/XFY2vmd8=
+github.com/frankban/quicktest v1.13.1/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
@@ -585,6 +588,7 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+github.com/google/renameio v1.0.1/go.mod h1:t/HQoYBZSsWSNK35C6CO/TpPLDVWvxOHboWUAweKUpk=
github.com/google/rpmpack v0.0.0-20191226140753-aa36bfddb3a0/go.mod h1:RaTPr0KUf2K7fnZYLNDrr8rxAamWs3iNywJLtQ2AzBg=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
@@ -726,6 +730,8 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
+github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
@@ -890,6 +896,7 @@ github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCko
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
+github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -947,6 +954,9 @@ github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
+github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
+github.com/rogpeppe/go-internal v1.8.1-0.20210923151022-86f73c517451 h1:d1PiN4RxzIFXCJTvRkvSkKqwtRAl5ZV4lATKtQI0B7I=
+github.com/rogpeppe/go-internal v1.8.1-0.20210923151022-86f73c517451/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rubiojr/go-vhd v0.0.0-20160810183302-0bfd3b39853c/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
@@ -1391,10 +1401,13 @@ golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210925032602-92d5a993a665 h1:QOQNt6vCjMpXE7JSK5VvAzJC1byuN3FgTNSBwf+CJgI=
+golang.org/x/sys v0.0.0-20210925032602-92d5a993a665/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210916214954-140adaaadfaf/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -1744,8 +1757,11 @@ modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=
modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=
modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs=
modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I=
+mvdan.cc/editorconfig v0.2.0/go.mod h1:lvnnD3BNdBYkhq+B4uBuFFKatfp02eB6HixDvEz91C0=
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc=
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4=
+mvdan.cc/sh/v3 v3.4.0 h1:thPCJ0hffn/Y/vMGs3HVMPYStNTyr2+lQee8LQiPZSU=
+mvdan.cc/sh/v3 v3.4.0/go.mod h1:p/tqPPI4Epfk2rICAe2RoaNd8HBSJ8t9Y2DA9yQlbzY=
mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZIVpwbkw+04kSxk3rAtzlimaUJw=
mvdan.cc/unparam v0.0.0-20200501210554-b37ab49443f7/go.mod h1:HGC5lll35J70Y5v7vCGb9oLhHoScFwkHDJm/05RdSTc=
pack.ag/amqp v0.11.2/go.mod h1:4/cbmt4EJXSKlG6LCfWHoqmN0uFdy5i/+YFz+fTfhV4=
diff --git a/internal/parser/debian.go b/internal/parser/debian.go
new file mode 100644
index 0000000..a5ece33
--- /dev/null
+++ b/internal/parser/debian.go
@@ -0,0 +1,201 @@
+/*
+Copyright © 2021 Drew Short
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package parser
+
+import (
+ "bytes"
+ "errors"
+ "strings"
+
+ log "github.com/sirupsen/logrus"
+
+ "mvdan.cc/sh/v3/syntax"
+)
+
+// DebianCommandParser the parser responsible for handling Debian based images
+type DebianCommandParser struct {
+ parser *syntax.Parser
+ printer *syntax.Printer
+}
+
+// newDebianCommandParser Provision a new instance of DebianCommandParser with required components
+func newDebianCommandParser() DebianCommandParser {
+ return DebianCommandParser{
+ parser: syntax.NewParser(),
+ printer: syntax.NewPrinter(),
+ }
+}
+
+// shellNodeToString Convert a node to the string representation
+func (c DebianCommandParser) shellNodeToString(node syntax.Node) string {
+ buffer := bytes.NewBufferString("")
+ err := c.printer.Print(buffer, node)
+ if err != nil {
+ log.Panic("Failed to convert node to string")
+ }
+ return buffer.String()
+}
+
+// isRedirectedToAptSourcesList Checks if the list of redirects contains the apt sources list directory
+func isRedirectedToAptSourcesList(redirects []*syntax.Redirect) bool {
+ if redirects != nil && len(redirects) > 0 {
+ for _, redirect := range redirects {
+ if strings.Contains(redirect.Word.Lit(), "/etc/apt/sources.list") {
+ return true
+ }
+ }
+ }
+ return false
+}
+
+// isAddRepositoryExpression Checks for expressions that add to the repository
+func isAddRepositoryExpression(command syntax.CallExpr, redirects []*syntax.Redirect) bool {
+ // Adding via debian tooling
+ if command.Args[0].Lit() == "add-apt-repository" {
+ return true
+ // Echoing into a sources file directly
+ } else if command.Args[0].Lit() == "echo" && isRedirectedToAptSourcesList(redirects) {
+ return true
+ }
+ return false
+}
+
+// trimQuotes return a string without surrounding quotes
+func trimQuotes(value string) string {
+ if value[0] == '"' && value[len(value)-1] == '"' {
+ return value[1 : len(value)-1]
+ } else if value[0] == '\'' && value[len(value)-1] == '\'' {
+ return value[1 : len(value)-1]
+ }
+ return value
+}
+
+// getAddedRepository parse the repository information from the shell commands
+func (c DebianCommandParser) getAddedRepository(command syntax.CallExpr, redirects []*syntax.Redirect) (string, error) {
+ // Adding via debian tooling
+ if command.Args[0].Lit() == "add-apt-repository" {
+ return command.Args[len(command.Args)-1].Lit(), nil
+ // Echoing into a sources file directly
+ } else if command.Args[0].Lit() == "echo" && isRedirectedToAptSourcesList(redirects) {
+ for _, argument := range command.Args {
+ var argumentNode syntax.Node = argument
+ argumentValue := trimQuotes(c.shellNodeToString(argumentNode))
+ if strings.Contains(argumentValue, "deb") || strings.Contains(argumentValue, "http://") {
+ return argumentValue, nil
+ }
+ }
+ }
+ return "", errors.New("failed to parse repository")
+}
+
+// GetRepositories Returns a list of Repository entries that were identified
+func (c DebianCommandParser) GetRepositories(command string) ([]*Repository, error) {
+ var repositories []*Repository
+
+ reader := strings.NewReader(command)
+ parsedScript, err := c.parser.Parse(reader, "")
+ if err != nil {
+ log.Fatalf("Failed to parse script \"%s\"", command)
+ return nil, err
+ }
+
+ syntax.Walk(parsedScript, func(node syntax.Node) bool {
+ switch parsedStatement := node.(type) {
+ case *syntax.Stmt:
+ switch parsedCommand := parsedStatement.Cmd.(type) {
+ case *syntax.CallExpr:
+ if isAddRepositoryExpression(*parsedCommand, parsedStatement.Redirs) {
+ repositoryInformation, err := c.getAddedRepository(*parsedCommand, parsedStatement.Redirs)
+ if err != nil {
+ log.Fatalf("Unexpected error while parsing repository from \"%s\": %s", c.shellNodeToString(node), err)
+ }
+ repositories = append(repositories, &Repository{Information: repositoryInformation})
+ log.Debugf("Found repository \"%s\"", repositoryInformation)
+ }
+ }
+ }
+ return true
+ })
+
+ return repositories, nil
+}
+
+// isAddRepositoryExpression Checks for expressions that add to the repository
+func isInstallerExpression(command syntax.CallExpr) bool {
+ // Adding via debian tooling
+ commandProgram := command.Args[0].Lit()
+ if (commandProgram == "apt-get" || commandProgram == "apt") && len(command.Args) > 1 && command.Args[1].Lit() == "install" {
+ return true
+ }
+ return false
+}
+
+func (c DebianCommandParser) getInstalledPinnedPackages(command syntax.CallExpr) ([]*Package, error) {
+ var packages []*Package
+
+ // Adding via debian tooling
+ commandProgram := command.Args[0].Lit()
+ if (commandProgram == "apt-get" || commandProgram == "apt") && len(command.Args) > 1 && command.Args[1].Lit() == "install" {
+ for _, argument := range command.Args {
+ var argumentNode syntax.Node = argument
+ argumentValue := trimQuotes(c.shellNodeToString(argumentNode))
+ if strings.Contains(argumentValue, "=") {
+ pinnedPackageParts := strings.SplitN(argumentValue, "=", 2)
+ packages = append(packages, &Package{
+ Name: pinnedPackageParts[0],
+ Version: pinnedPackageParts[1],
+ })
+ }
+ }
+ return packages, nil
+ }
+ return packages, errors.New("failed to parse packages")
+}
+
+// GetPinnedPackages get a list of all pinned packages
+func (c DebianCommandParser) GetPinnedPackages(command string) ([]*Package, error) {
+ var packages []*Package
+
+ reader := strings.NewReader(command)
+ parsedScript, err := c.parser.Parse(reader, "")
+ if err != nil {
+ log.Fatalf("Failed to parse script \"%s\"", command)
+ return nil, err
+ }
+
+ syntax.Walk(parsedScript, func(node syntax.Node) bool {
+ switch parsedStatement := node.(type) {
+ case *syntax.Stmt:
+ switch parsedCommand := parsedStatement.Cmd.(type) {
+ case *syntax.CallExpr:
+ if isInstallerExpression(*parsedCommand) {
+ pinnedPackages, err := c.getInstalledPinnedPackages(*parsedCommand)
+ if err != nil {
+ log.Fatalf("Unexpected error while parsing pinned packages from \"%s\": %s", c.shellNodeToString(node), err)
+ }
+ for _, pinnedPackage := range pinnedPackages {
+ packages = append(packages, pinnedPackage)
+ log.Debugf("Found pinned package \"%s=%s\"", pinnedPackage.Name, pinnedPackage.Version)
+ }
+ }
+ }
+ }
+ return true
+ })
+
+ return packages, nil
+}
diff --git a/internal/parser/dockerfile/dockerfile.go b/internal/parser/dockerfile/dockerfile.go
index 82652f8..455aece 100644
--- a/internal/parser/dockerfile/dockerfile.go
+++ b/internal/parser/dockerfile/dockerfile.go
@@ -102,14 +102,20 @@ func parseDockerfileStage(dockerfileStage buildkitDockerfileInstructions.Stage)
Tag: imageParts[1],
}
- repositories, err := parseRepositoriesFromDockerfileStage(dockerfileStage)
+ commandParser, err := parser.GetCommandParser(image)
+ if err != nil {
+ log.Fatalf("Unexpected error while determining command parser for image %s", image)
+ return parser.Stage{}, err
+ }
+
+ repositories, err := parseRepositoriesFromDockerfileStage(dockerfileStage, commandParser)
if err != nil {
message := fmt.Sprintf("Unexpected error while parsing repositories from stage")
log.Fatal(message)
return parser.Stage{}, errors.New(message)
}
- packages, err := parsePackagesFromDockerfileStage(dockerfileStage)
+ packages, err := parsePackagesFromDockerfileStage(dockerfileStage, commandParser)
if err != nil {
message := fmt.Sprintf("Unexpected error while parsing packages from stage")
log.Fatal(message)
@@ -124,12 +130,44 @@ func parseDockerfileStage(dockerfileStage buildkitDockerfileInstructions.Stage)
}, nil
}
-func parsePackagesFromDockerfileStage(_ buildkitDockerfileInstructions.Stage) ([]*parser.Package, error) {
- var packages []*parser.Package
- return packages, nil
-}
-
-func parseRepositoriesFromDockerfileStage(_ buildkitDockerfileInstructions.Stage) ([]*parser.Repository, error) {
+func parseRepositoriesFromDockerfileStage(dockerfileStage buildkitDockerfileInstructions.Stage, commandParser *parser.CommandParser) ([]*parser.Repository, error) {
var repositories []*parser.Repository
+ typedCommandParser := *commandParser
+
+ for _, command := range dockerfileStage.Commands {
+ switch command.(type) {
+ case *buildkitDockerfileInstructions.RunCommand:
+ runCommand := command.(*buildkitDockerfileInstructions.RunCommand)
+
+ log.Tracef("Parsing RunCommand \"%s\" for repositories", runCommand.CmdLine)
+
+ _, err := typedCommandParser.GetRepositories(runCommand.CmdLine[0])
+ if err != nil {
+ return nil, err
+ }
+ }
+ }
+
return repositories, nil
}
+
+func parsePackagesFromDockerfileStage(dockerfileStage buildkitDockerfileInstructions.Stage, commandParser *parser.CommandParser) ([]*parser.Package, error) {
+ var packages []*parser.Package
+ typedCommandParser := *commandParser
+
+ for _, command := range dockerfileStage.Commands {
+ switch command.(type) {
+ case *buildkitDockerfileInstructions.RunCommand:
+ runCommand := command.(*buildkitDockerfileInstructions.RunCommand)
+
+ log.Tracef("Parsing RunCommand \"%s\" for packages", runCommand.CmdLine)
+
+ _, err := typedCommandParser.GetPinnedPackages(runCommand.CmdLine[0])
+ if err != nil {
+ return nil, err
+ }
+ }
+ }
+
+ return packages, nil
+}
diff --git a/internal/parser/parser.go b/internal/parser/parser.go
index b0ffc29..f9c78e0 100644
--- a/internal/parser/parser.go
+++ b/internal/parser/parser.go
@@ -17,6 +17,13 @@ limitations under the License.
// Package parser implements the required components for working with image definitions
package parser
+import (
+ "errors"
+ "fmt"
+
+ log "github.com/sirupsen/logrus"
+)
+
// ParseResult is the standard representation for am image declaration
type ParseResult struct {
Stages []*Stage
@@ -44,5 +51,66 @@ type Package struct {
// Repository is the representation of an added repository defined in a stage declaration
type Repository struct {
- Path string
+ Information string
+}
+
+// CommandParserType represents the recognized type of parser usable for a specific image
+type CommandParserType int
+
+const (
+ CommandParserTypeDebian CommandParserType = iota
+)
+
+// CommandParserTypeIds is a lookup map between CommandParserType and a representation string
+var commandParserTypeIds = map[CommandParserType][]string{
+ CommandParserTypeDebian: {"debian"},
+}
+
+func lookupCommandParserTypeName(parserType CommandParserType) string {
+ if parserTypeName, ok := commandParserTypeIds[parserType]; ok {
+ return parserTypeName[0]
+ } else {
+ return "UNKNOWN"
+ }
+}
+
+var availableCommandParsers = map[CommandParserType]CommandParser{}
+
+// CommandParser handles parsing commands from RUN arguments dependent on the base image type
+type CommandParser interface {
+ GetRepositories(runCommand string) ([]*Repository, error)
+ GetPinnedPackages(runCommand string) ([]*Package, error)
+}
+
+// InitCommandParsers exposes functionality to configure the behavior of mapping images to a CommandParser
+func InitCommandParsers() error {
+
+ // Registering known parsers
+ availableCommandParsers[CommandParserTypeDebian] = newDebianCommandParser()
+
+ return nil
+}
+
+func GetCommandParser(image Image) (*CommandParser, error) {
+ parserType, err := determineParserForImage(image)
+ if err != nil {
+ log.Fatalf("Failed to determine a matching CommandParser for %s: %s", image, err)
+ return nil, err
+ }
+
+ if requestedCommandParser, ok := availableCommandParsers[parserType]; ok {
+ return &requestedCommandParser, nil
+ } else {
+ message := fmt.Sprintf("Unable to identify parser for %s", lookupCommandParserTypeName(parserType))
+ log.Fatal(message)
+ return nil, errors.New(message)
+ }
+}
+
+// determineParserForImage matches image information to the correct CommandParserType
+func determineParserForImage(_ Image) (CommandParserType, error) {
+
+ // TODO: Map image information to the list of command parsers supported
+
+ return CommandParserTypeDebian, nil
}
diff --git a/test/data/old_pinned_dependencies_ubuntu.Dockerfile b/test/data/old_pinned_dependencies_ubuntu.Dockerfile
index 23abb03..51f62d9 100644
--- a/test/data/old_pinned_dependencies_ubuntu.Dockerfile
+++ b/test/data/old_pinned_dependencies_ubuntu.Dockerfile
@@ -1,7 +1,8 @@
FROM ubuntu:18.04
-RUN apt-get update && apt-get install \
+RUN echo "deb http://archive.canonical.com/ubuntu bionic partner" > /etc/apt/sources.list.d/partner.list \
+ && apt-get update && apt-get install \
ca-certificates=20210119~18.04.1 \
curl=7.58.0-2ubuntu3.15 \
&& apt-get clean \
- && rm -rf /var/lib/apt/lists/*
\ No newline at end of file
+ && rm -rf /var/lib/apt/lists/* \
\ No newline at end of file