diff --git a/build/docker/alpine.Dockerfile b/build/docker/alpine.Dockerfile index 320ae487..44b61123 100644 --- a/build/docker/alpine.Dockerfile +++ b/build/docker/alpine.Dockerfile @@ -9,7 +9,7 @@ RUN mkdir -p internal/file/embedded && \ RUN apk add --no-cache make curl && make install && apk del make curl CMD [ "debricked" ] -FROM alpine:latest AS cli-base +FROM alpine:3.21 AS cli-base ENV DEBRICKED_TOKEN="" RUN apk add --no-cache git WORKDIR /root/ @@ -60,7 +60,7 @@ RUN apk --no-cache --update add \ curl \ bash -RUN apk --no-cache --update add dotnet8-sdk go~=1.23 --repository=https://dl-cdn.alpinelinux.org/alpine/v3.20/community +RUN apk --no-cache --update add dotnet8-sdk go~=1.23 --repository=https://dl-cdn.alpinelinux.org/alpine/v3.21/community RUN dotnet --version && npm -v && yarn -v diff --git a/build/docker/debian.Dockerfile b/build/docker/debian.Dockerfile index 3d28c687..586bee35 100644 --- a/build/docker/debian.Dockerfile +++ b/build/docker/debian.Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.23-bookworm AS dev +FROM golang:1.23.4-bookworm AS dev WORKDIR /cli ARG DEBIAN_FRONTEND=noninteractive @@ -90,18 +90,23 @@ RUN curl -fsSLO https://dot.net/v1/dotnet-install.sh \ && rm ./dotnet-install.sh \ && dotnet help -ENV GOLANG_VERSION="1.23" +ENV GOLANG_VERSION="1.23.4" +ENV GOPATH="/usr/lib/go" +ENV PATH="$GOPATH/bin:$PATH" RUN apt -y update && apt -y upgrade && apt -y install \ - ca-certificates && \ + ca-certificates \ + wget && \ apt -y install -t unstable \ - python3.12\ - python3.12-venv \ - golang-$GOLANG_VERSION \ + python3.13 \ + python3.13-venv \ openjdk-21-jdk && \ apt -y clean && rm -rf /var/lib/apt/lists/* && \ - # Symlink go binary to bin directory which is in path - ln -s /usr/lib/go-$GOLANG_VERSION/bin/go /usr/bin/go && \ - ln -s /usr/bin/python3.12 /usr/bin/python + # Install Go manually from official source + wget https://go.dev/dl/go${GOLANG_VERSION}.linux-amd64.tar.gz && \ + tar -C /usr/local -xzf go${GOLANG_VERSION}.linux-amd64.tar.gz && \ + rm go${GOLANG_VERSION}.linux-amd64.tar.gz && \ + ln -s /usr/local/go/bin/go /usr/bin/go && \ + ln -s /usr/bin/python3.13 /usr/bin/python RUN dotnet --version @@ -132,7 +137,7 @@ RUN apt -y update && apt -y install \ RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/bin --filename=composer -RUN ln -sf /usr/bin/python3.12 /usr/bin/python3 && php -v && composer --version && python3 --version +RUN ln -sf /usr/bin/python3.13 /usr/bin/python3 && php -v && composer --version && python3 --version CMD [ "debricked", "scan" ] diff --git a/internal/upload/batch.go b/internal/upload/batch.go index c95a8bd9..0bf23864 100644 --- a/internal/upload/batch.go +++ b/internal/upload/batch.go @@ -322,7 +322,18 @@ type purlConfig struct { } type DebrickedConfig struct { - Overrides []purlConfig `json:"overrides" yaml:"overrides"` + Overrides []purlConfig `json:"override,omitempty" yaml:"overrides"` + Ignore *IgnoreConfig `json:"ignore,omitempty" yaml:"ignore,omitempty"` +} + +// IgnoreConfig matches the structure of the 'ignore' section in YAML +type IgnoreConfig struct { + Packages []IgnorePackage `json:"packages" yaml:"packages"` +} + +type IgnorePackage struct { + PURL string `json:"pURL" yaml:"pURL"` + Version string `json:"version,omitempty" yaml:"version,omitempty"` } type uploadFinish struct { @@ -361,8 +372,43 @@ type DebrickedConfigYAML struct { Overrides []pURLConfigYAML `yaml:"overrides"` } -func GetDebrickedConfig(path string) *DebrickedConfig { +// extractIgnore unmarshals the ignore section from raw config +func extractIgnore(raw map[string]interface{}) *IgnoreConfig { + if rawIgnore, ok := raw["ignore"]; ok { + ignoreYaml, err := yaml.Marshal(rawIgnore) + if err == nil { + var ignoreObj IgnoreConfig + if yaml.Unmarshal(ignoreYaml, &ignoreObj) == nil { + return &ignoreObj + } + } + } + + return nil +} + +// convertOverrides converts YAML overrides to purlConfig slice +func convertOverrides(yamlOverrides []pURLConfigYAML) []purlConfig { var overrides []purlConfig + for _, entry := range yamlOverrides { + var version string + var exist bool + pURL := entry.PackageURL + fileRegexes := entry.FileRegexes + if entry.Version == nil { + version = "" + exist = false + } else { + version = *entry.Version + exist = true + } + overrides = append(overrides, purlConfig{PackageURL: pURL, Version: boolOrString{Version: version, HasVersion: exist}, FileRegexes: fileRegexes}) + } + + return overrides +} + +func GetDebrickedConfig(path string) *DebrickedConfig { var yamlConfig DebrickedConfigYAML yamlFile, err := os.ReadFile(path) if err != nil { @@ -374,7 +420,10 @@ func GetDebrickedConfig(path string) *DebrickedConfig { return nil } - err = yaml.Unmarshal(yamlFile, &yamlConfig) + + // Unmarshal into map to support any key for overrides and ignore + var raw map[string]interface{} + err = yaml.Unmarshal(yamlFile, &raw) if err != nil { fmt.Printf("%s Failed to unmarshal debricked config: \"%s\"\n", color.YellowString("⚠️"), @@ -383,22 +432,41 @@ func GetDebrickedConfig(path string) *DebrickedConfig { return nil } - for _, entry := range yamlConfig.Overrides { - var version string - var exist bool - pURL := entry.PackageURL - fileRegexes := entry.FileRegexes - if entry.Version == nil { - version = "" - exist = false - } else { - version = *entry.Version - exist = true + + // Accept any key for overrides, normalize to 'overrides' + for k, v := range raw { + lower := strings.ToLower(k) + if lower == "overrides" || lower == "override" { + raw["overrides"] = v } - overrides = append(overrides, purlConfig{PackageURL: pURL, Version: boolOrString{Version: version, HasVersion: exist}, FileRegexes: fileRegexes}) } + // Marshal back to YAML and unmarshal into struct + fixedYaml, err := yaml.Marshal(raw) + if err != nil { + fmt.Printf("%s Failed to re-marshal config: \"%s\"\n", + color.YellowString("⚠️"), + color.RedString(err.Error()), + ) + + return nil + } + + err = yaml.Unmarshal(fixedYaml, &yamlConfig) + if err != nil { + fmt.Printf("%s Failed to unmarshal debricked config: \"%s\"\n", + color.YellowString("⚠️"), + color.RedString(err.Error()), + ) + + return nil + } + + ignore := extractIgnore(raw) + overrides := convertOverrides(yamlConfig.Overrides) + return &DebrickedConfig{ Overrides: overrides, + Ignore: ignore, } } diff --git a/internal/upload/batch_test.go b/internal/upload/batch_test.go index 73fafe47..16ce2c10 100644 --- a/internal/upload/batch_test.go +++ b/internal/upload/batch_test.go @@ -176,6 +176,52 @@ func TestGetDebrickedConfig(t *testing.T) { assert.JSONEq(t, string(configJSON), string(expectedJSON)) } +func TestGetDebrickedConfigIgnore(t *testing.T) { + config := GetDebrickedConfig(filepath.Join("testdata", "debricked-config-ignore.yaml")) + configJSON, err := json.Marshal(config) + assert.Nil(t, err) + expectedJSON, err := json.Marshal(DebrickedConfig{ + Ignore: &IgnoreConfig{ + Packages: []IgnorePackage{ + {PURL: "pkg:npm/verdaccio", Version: "3.7.0"}, + {PURL: "pkg:npm/chart.js"}, + {PURL: "pkg:nuget/simpleinjector", Version: "4.7.1"}, + }, + }, + }) + assert.Nil(t, err) + assert.JSONEq(t, string(configJSON), string(expectedJSON)) +} + +func TestGetDebrickedConfigOverridesIgnore(t *testing.T) { + config := GetDebrickedConfig(filepath.Join("testdata", "debricked-config-override-ignore.yaml")) + configJSON, err := json.Marshal(config) + assert.Nil(t, err) + expectedJSON, err := json.Marshal(DebrickedConfig{ + Overrides: []purlConfig{ + { + PackageURL: "pkg:npm/lodash", + Version: boolOrString{Version: "1.0.0", HasVersion: true}, + FileRegexes: []string{".*/lodash/.*"}, + }, + { + PackageURL: "pkg:maven/org.openjfx/javafx-base", + Version: boolOrString{Version: "", HasVersion: false}, + FileRegexes: []string{"subpath/org.openjfx/.*"}, + }, + }, + Ignore: &IgnoreConfig{ + Packages: []IgnorePackage{ + {PURL: "pkg:npm/verdaccio", Version: "3.7.0"}, + {PURL: "pkg:npm/chart.js"}, + {PURL: "pkg:nuget/simpleinjector", Version: "4.7.1"}, + }, + }, + }) + assert.Nil(t, err) + assert.JSONEq(t, string(configJSON), string(expectedJSON)) +} + func TestGetDebrickedConfigUnmarshalError(t *testing.T) { config := GetDebrickedConfig(filepath.Join("testdata", "debricked-config-error.yaml")) configJSON, err := json.Marshal(config) @@ -209,7 +255,58 @@ func TestMarshalJSONDebrickedConfig(t *testing.T) { }, }, }) - expectedJSON := "{\"overrides\":[{\"pURL\":\"pkg:npm/lodash\",\"version\":\"1.0.0\",\"fileRegexes\":[\".*/lodash/.*\"]},{\"pURL\":\"pkg:maven/org.openjfx/javafx-base\",\"version\":false,\"fileRegexes\":[\"subpath/org.openjfx/.*\"]}]}" + expectedJSON := "{\"override\":[{\"pURL\":\"pkg:npm/lodash\",\"version\":\"1.0.0\",\"fileRegexes\":[\".*/lodash/.*\"]},{\"pURL\":\"pkg:maven/org.openjfx/javafx-base\",\"version\":false,\"fileRegexes\":[\"subpath/org.openjfx/.*\"]}]}" + assert.Nil(t, err) + assert.Equal(t, []byte(expectedJSON), config) +} + +func TestMarshalJSONDebrickedConfigIgnoreOnly(t *testing.T) { + config, err := json.Marshal(DebrickedConfig{ + Ignore: &IgnoreConfig{ + Packages: []IgnorePackage{ + {PURL: "pkg:npm/verdaccio", Version: "3.7.0"}, + {PURL: "pkg:npm/chart.js"}, + }, + }, + }) + expectedJSON := "{\"ignore\":{\"packages\":[{\"pURL\":\"pkg:npm/verdaccio\",\"version\":\"3.7.0\"},{\"pURL\":\"pkg:npm/chart.js\"}]}}" assert.Nil(t, err) assert.Equal(t, []byte(expectedJSON), config) } + +func TestMarshalJSONDebrickedConfigBoth(t *testing.T) { + config, err := json.Marshal(DebrickedConfig{ + Overrides: []purlConfig{ + { + PackageURL: "pkg:npm/lodash", + Version: boolOrString{Version: "1.0.0", HasVersion: true}, + FileRegexes: []string{".*/lodash/.*"}, + }, + }, + Ignore: &IgnoreConfig{ + Packages: []IgnorePackage{ + {PURL: "pkg:npm/chart.js"}, + }, + }, + }) + expectedJSON := "{\"override\":[{\"pURL\":\"pkg:npm/lodash\",\"version\":\"1.0.0\",\"fileRegexes\":[\".*/lodash/.*\"]}],\"ignore\":{\"packages\":[{\"pURL\":\"pkg:npm/chart.js\"}]}}" + assert.Nil(t, err) + assert.Equal(t, []byte(expectedJSON), config) +} + +func TestGetDebrickedConfigSingularOverride(t *testing.T) { + config := GetDebrickedConfig(filepath.Join("testdata", "debricked-config-singular-override.yaml")) + configJSON, err := json.Marshal(config) + assert.Nil(t, err) + expectedJSON, err := json.Marshal(DebrickedConfig{ + Overrides: []purlConfig{ + { + PackageURL: "pkg:npm/lodash", + Version: boolOrString{Version: "1.0.0", HasVersion: true}, + FileRegexes: []string{".*/lodash/.*"}, + }, + }, + }) + assert.Nil(t, err) + assert.JSONEq(t, string(configJSON), string(expectedJSON)) +} diff --git a/internal/upload/testdata/debricked-config-ignore.yaml b/internal/upload/testdata/debricked-config-ignore.yaml new file mode 100644 index 00000000..83ce0ed1 --- /dev/null +++ b/internal/upload/testdata/debricked-config-ignore.yaml @@ -0,0 +1,7 @@ +ignore: + packages: + - pURL: "pkg:npm/verdaccio" + version: "3.7.0" + - pURL: "pkg:npm/chart.js" + - pURL: "pkg:nuget/simpleinjector" + version: "4.7.1" \ No newline at end of file diff --git a/internal/upload/testdata/debricked-config-override-ignore.yaml b/internal/upload/testdata/debricked-config-override-ignore.yaml new file mode 100644 index 00000000..d891a297 --- /dev/null +++ b/internal/upload/testdata/debricked-config-override-ignore.yaml @@ -0,0 +1,15 @@ +overrides: + - pURL: "pkg:npm/lodash" + version: "1.0.0" # optional: if left out, we will decide version + fileRegexes: + - ".*/lodash/.*" # PCRE2 + - pURL: "pkg:maven/org.openjfx/javafx-base" + fileRegexes: + - "subpath/org.openjfx/.*" +ignore: + packages: + - pURL: "pkg:npm/verdaccio" + version: "3.7.0" + - pURL: "pkg:npm/chart.js" + - pURL: "pkg:nuget/simpleinjector" + version: "4.7.1" \ No newline at end of file diff --git a/internal/upload/testdata/debricked-config-singular-override.yaml b/internal/upload/testdata/debricked-config-singular-override.yaml new file mode 100644 index 00000000..558e1c72 --- /dev/null +++ b/internal/upload/testdata/debricked-config-singular-override.yaml @@ -0,0 +1,5 @@ +override: + - pURL: "pkg:npm/lodash" + version: "1.0.0" + fileRegexes: + - ".*/lodash/.*"