Skip to content

Commit

Permalink
Backport #304
Browse files Browse the repository at this point in the history
Signed-off-by: Daniel Mikusa <[email protected]>
  • Loading branch information
dmikusa committed Nov 11, 2024
1 parent ada8ea8 commit a82b461
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 13 deletions.
51 changes: 38 additions & 13 deletions layer.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,29 +166,54 @@ func (l *LayerContributor) checkIfMetadataMatches(layer libcnb.Layer) (map[strin
}

func (l *LayerContributor) Equals(expectedM map[string]interface{}, layerM map[string]interface{}) (bool, error) {
if dep, ok := expectedM["dependency"].(map[string]interface{}); ok {
for k, v := range dep {
if k == "deprecation_date" {
deprecationDate := v.(time.Time).Truncate(time.Second).In(time.UTC)
dep["deprecation_date"] = deprecationDate
break
}
}
// TODO Do we want the Equals method to modify the underlying maps? Else we need to make a copy here.

if err := l.normalizeDependencyDeprecationDate(expectedM); err != nil {
return false, fmt.Errorf("%w (expected layer)", err)
}

if err := l.normalizeDependencyDeprecationDate(layerM); err != nil {
return false, fmt.Errorf("%w (actual layer)", err)
}
if dep, ok := layerM["dependency"].(map[string]interface{}); ok {

return reflect.DeepEqual(expectedM, layerM), nil
}

// normalizeDependencyDeprecationDate makes sure the dependency deprecation date is represented as a time.Time object
// in the map whenever it exists.
func (l *LayerContributor) normalizeDependencyDeprecationDate(input map[string]interface{}) error {
if dep, ok := input["dependency"].(map[string]interface{}); ok {
for k, v := range dep {
if k == "deprecation_date" {
deprecationDate, err := time.Parse(time.RFC3339, v.(string))
deprecationDate, err := l.parseDeprecationDate(v)
if err != nil {
return false, fmt.Errorf("unable to parse deprecation_date %s", v.(string))
return err
}
deprecationDate = deprecationDate.Truncate(time.Second).In(time.UTC)
dep["deprecation_date"] = deprecationDate
break
}
}
}
return reflect.DeepEqual(expectedM, layerM), nil
return nil
}

// parseDeprecationDate accepts both string and time.Time as input, and returns
// a truncated time.Time value.
func (l *LayerContributor) parseDeprecationDate(v interface{}) (deprecationDate time.Time, err error) {
switch vDate := v.(type) {
case time.Time:
deprecationDate = vDate
case string:
deprecationDate, err = time.Parse(time.RFC3339, vDate)
if err != nil {
return time.Time{}, fmt.Errorf("unable to parse deprecation_date %s", vDate)
}
default:
return time.Time{}, fmt.Errorf("unexpected type %T for deprecation_date %v", v, v)
}

deprecationDate = deprecationDate.Truncate(time.Second).In(time.UTC)
return
}

func (l *LayerContributor) checkIfLayerRestored(layer libcnb.Layer) (bool, error) {
Expand Down
82 changes: 82 additions & 0 deletions layer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,88 @@ func testLayer(t *testing.T, context spec.G, it spec.S) {
Expect(called).To(BeFalse())
})

it("gracefully handles a deprecationDate in time.Time format in actual layer metadata", func() {
// reusing It: does not call function with non-matching deprecation_date format
// but this time with a deprecationDate formatted as time.Time in the actual layer metadata
actualDeprecationDate, _ := time.Parse(time.RFC3339, "2021-04-01T00:00:00Z")

dependency = libpak.BuildModuleDependency{
ID: "test-id",
Name: "test-name",
Version: "1.1.1",
URI: fmt.Sprintf("%s/test-path", server.URL()),
SHA256: "576dd8416de5619ea001d9662291d62444d1292a38e96956bc4651c01f14bca1",
Stacks: []string{"test-stack"},
Licenses: []libpak.BuildModuleDependencyLicense{
{
Type: "test-type",
URI: "test-uri",
},
},
CPEs: []string{"cpe:2.3:a:some:jre:11.0.2:*:*:*:*:*:*:*"},
PURL: "pkg:generic/[email protected]?arch=amd64",
DeprecationDate: dependency.DeprecationDate, // parsed as '2021-04-01 00:00:00 +0000 UTC'
}
dlc.ExpectedMetadata = map[string]interface{}{"dependency": dependency}

layer.Metadata = map[string]interface{}{"dependency": map[string]interface{}{
"id": dependency.ID,
"name": dependency.Name,
"version": dependency.Version,
"uri": dependency.URI,
"sha256": dependency.SHA256,
"stacks": []interface{}{dependency.Stacks[0]},
"licenses": []map[string]interface{}{
{
"type": dependency.Licenses[0].Type,
"uri": dependency.Licenses[0].URI,
},
},
"cpes": []interface{}{"cpe:2.3:a:some:jre:11.0.2:*:*:*:*:*:*:*"},
"purl": "pkg:generic/[email protected]?arch=amd64",
"deprecation_date": actualDeprecationDate, // does not match without truncation
}}

var called bool

err := dlc.Contribute(layer, func(layer *libcnb.Layer, artifact *os.File) error {
defer artifact.Close()

called = true
return nil
})
Expect(err).NotTo(HaveOccurred())

Expect(called).To(BeFalse())
})

it("does not panic on unsupported deprecationDate format in layer metadata", func() {
// Unexpected type (not string or time.Time)
actualDeprecationDate := 1234

dependency = libpak.BuildModuleDependency{
ID: "test-id",
DeprecationDate: dependency.DeprecationDate, // parsed as '2021-04-01 00:00:00 +0000 UTC'
}
dlc.ExpectedMetadata = map[string]interface{}{"dependency": dependency}

layer.Metadata = map[string]interface{}{"dependency": map[string]interface{}{
"id": dependency.ID,
"deprecation_date": actualDeprecationDate, // does not match without truncation
}}

var called bool

err := dlc.Contribute(layer, func(layer *libcnb.Layer, artifact *os.File) error {
defer artifact.Close()

called = true
return nil
})
Expect(err).To(MatchError(ContainSubstring("unexpected type int for deprecation_date")))
Expect(called).To(BeFalse())
})

it("does not call function with missing deprecation_date", func() {
dependency = libpak.BuildModuleDependency{
ID: "test-id",
Expand Down

0 comments on commit a82b461

Please sign in to comment.