Skip to content

Commit bc000d0

Browse files
committed
feat(config): Add fingerprint ignore tags to some config fields
1 parent 695fb24 commit bc000d0

File tree

5 files changed

+125
-12
lines changed

5 files changed

+125
-12
lines changed

internal/projectconfig/build.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ type CheckConfig struct {
1313
// Skip indicates whether the %check section should be disabled for this component.
1414
Skip bool `toml:"skip,omitempty" json:"skip,omitempty" jsonschema:"title=Skip check,description=Disables the %check section by prepending 'exit 0' when set to true"`
1515
// SkipReason provides a required justification when Skip is true.
16-
SkipReason string `toml:"skip_reason,omitempty" json:"skipReason,omitempty" jsonschema:"title=Skip reason,description=Required justification for skipping the %check section"`
16+
SkipReason string `toml:"skip_reason,omitempty" json:"skipReason,omitempty" jsonschema:"title=Skip reason,description=Required justification for skipping the %check section" fingerprint:"-"`
1717
}
1818

1919
// Validate checks that required fields are set when Skip is true.
@@ -43,27 +43,27 @@ type ComponentBuildConfig struct {
4343
// Check section configuration.
4444
Check CheckConfig `toml:"check,omitempty" json:"check,omitempty" jsonschema:"title=Check configuration,description=Configuration for the %check section"`
4545
// Failure configuration and policy for this component's build.
46-
Failure ComponentBuildFailureConfig `toml:"failure,omitempty" json:"failure,omitempty" jsonschema:"title=Build failure configuration,description=Configuration and policy regarding build failures for this component."`
46+
Failure ComponentBuildFailureConfig `toml:"failure,omitempty" json:"failure,omitempty" jsonschema:"title=Build failure configuration,description=Configuration and policy regarding build failures for this component." fingerprint:"-"`
4747
// Hints for how or when to build the component; must not be required for correctness of builds.
48-
Hints ComponentBuildHints `toml:"hints,omitempty" json:"hints,omitempty" jsonschema:"title=Build hints,description=Non-essential hints for how or when to build the component."`
48+
Hints ComponentBuildHints `toml:"hints,omitempty" json:"hints,omitempty" jsonschema:"title=Build hints,description=Non-essential hints for how or when to build the component." fingerprint:"-"`
4949
}
5050

5151
// ComponentBuildFailureConfig encapsulates configuration and policy regarding a component's
5252
// build failure.
5353
type ComponentBuildFailureConfig struct {
5454
// Expected indicates that this component is expected to fail building. This is intended to be used as a temporary
5555
// marker for components that are expected to fail until they can be fixed.
56-
Expected bool `toml:"expected,omitempty" json:"expected,omitempty" jsonschema:"title=Expected failure,description=Indicates that this component is expected to fail building."`
56+
Expected bool `toml:"expected,omitempty" json:"expected,omitempty" jsonschema:"title=Expected failure,description=Indicates that this component is expected to fail building." fingerprint:"-"`
5757
// ExpectedReason provides a required justification when Expected is true.
58-
ExpectedReason string `toml:"expected-reason,omitempty" json:"expectedReason,omitempty" jsonschema:"title=Expected failure reason,description=Required justification for why this component is expected to fail building."`
58+
ExpectedReason string `toml:"expected-reason,omitempty" json:"expectedReason,omitempty" jsonschema:"title=Expected failure reason,description=Required justification for why this component is expected to fail building." fingerprint:"-"`
5959
}
6060

6161
// ComponentBuildHints encapsulates non-essential hints for how or when to build a component.
6262
// These are not required for correctness of builds, but may be used by tools to provide guidance
6363
// or optimizations.
6464
type ComponentBuildHints struct {
6565
// Expensive indicates that building this component is relatively expensive compared to the rest of the distro.
66-
Expensive bool `toml:"expensive,omitempty" json:"expensive,omitempty" jsonschema:"title=Expensive to build,description=Indicates that building this component is expensive and should be carefully considered when scheduling."`
66+
Expensive bool `toml:"expensive,omitempty" json:"expensive,omitempty" jsonschema:"title=Expensive to build,description=Indicates that building this component is expensive and should be carefully considered when scheduling." fingerprint:"-"`
6767
}
6868

6969
// Validate checks that the build configuration is valid.

internal/projectconfig/component.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ type Origin struct {
4949
// SourceFileReference encapsulates a reference to a specific source file artifact.
5050
type SourceFileReference struct {
5151
// Reference to the component to which the source file belongs.
52-
Component ComponentReference `toml:"-" json:"-"`
52+
Component ComponentReference `toml:"-" json:"-" fingerprint:"-"`
5353

5454
// Name of the source file; must be non-empty.
5555
Filename string `toml:"filename" json:"filename"`
@@ -61,7 +61,7 @@ type SourceFileReference struct {
6161
HashType fileutils.HashType `toml:"hash-type,omitempty" json:"hashType,omitempty"`
6262

6363
// Origin for this source file. When omitted, the file is resolved via the lookaside cache.
64-
Origin Origin `toml:"origin,omitempty" json:"origin,omitempty"`
64+
Origin Origin `toml:"origin,omitempty" json:"origin,omitempty" fingerprint:"-"`
6565
}
6666

6767
// Defines a component group. Component groups are logical groupings of components (see [ComponentConfig]).
@@ -111,11 +111,11 @@ func (g ComponentGroupConfig) WithAbsolutePaths(referenceDir string) ComponentGr
111111
// Defines a component.
112112
type ComponentConfig struct {
113113
// The component's name; not actually present in serialized files.
114-
Name string `toml:"-" json:"name" table:",sortkey"`
114+
Name string `toml:"-" json:"name" table:",sortkey" fingerprint:"-"`
115115

116116
// Reference to the source config file that this definition came from; not present
117117
// in serialized files.
118-
SourceConfigFile *ConfigFile `toml:"-" json:"-" table:"-"`
118+
SourceConfigFile *ConfigFile `toml:"-" json:"-" table:"-" fingerprint:"-"`
119119

120120
// Where to get its spec and adjacent files from.
121121
Spec SpecSource `toml:"spec,omitempty" json:"spec,omitempty" jsonschema:"title=Spec,description=Identifies where to find the spec for this component"`

internal/projectconfig/distro.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ type DistroReference struct {
1818
// Version of the referenced distro.
1919
Version string `toml:"version,omitempty" json:"version,omitempty" jsonschema:"title=Version,description=Version of the referenced distro"`
2020
// Snapshot date/time for source code if specified components will use source as it existed at this time.
21-
Snapshot string `toml:"snapshot,omitempty" json:"snapshot,omitempty" jsonschema:"format=date-time,title=Snapshot,description=If specified use source code as it existed at this date/time"`
21+
Snapshot string `toml:"snapshot,omitempty" json:"snapshot,omitempty" jsonschema:"format=date-time,title=Snapshot,description=If specified use source code as it existed at this date/time" fingerprint:"-"`
2222
}
2323

2424
// Implements the [Stringer] interface for [DistroReference].
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
package projectconfig_test
5+
6+
import (
7+
"reflect"
8+
"testing"
9+
10+
"github.com/microsoft/azure-linux-dev-tools/internal/projectconfig"
11+
"github.com/stretchr/testify/assert"
12+
)
13+
14+
// TestAllFingerprintedFieldsHaveDecision verifies that every field in every
15+
// fingerprinted struct has been consciously categorized as either included
16+
// (no fingerprint tag) or excluded (`fingerprint:"-"`).
17+
//
18+
// This test serves two purposes:
19+
// 1. It ensures that newly added fields default to **included** in the fingerprint
20+
// (the safe default — you get a false positive, never a false negative).
21+
// 2. It catches accidental removal of `fingerprint:"-"` tags from excluded fields,
22+
// since all exclusions are tracked in expectedExclusions.
23+
func TestAllFingerprintedFieldsHaveDecision(t *testing.T) {
24+
// All struct types whose fields participate in component fingerprinting.
25+
// When adding a new struct that feeds into the fingerprint, add it here.
26+
fingerprintedStructs := []reflect.Type{
27+
reflect.TypeFor[projectconfig.ComponentConfig](),
28+
reflect.TypeFor[projectconfig.ComponentBuildConfig](),
29+
reflect.TypeFor[projectconfig.CheckConfig](),
30+
reflect.TypeFor[projectconfig.ComponentBuildFailureConfig](),
31+
reflect.TypeFor[projectconfig.ComponentBuildHints](),
32+
reflect.TypeFor[projectconfig.ComponentOverlay](),
33+
reflect.TypeFor[projectconfig.SpecSource](),
34+
reflect.TypeFor[projectconfig.DistroReference](),
35+
reflect.TypeFor[projectconfig.SourceFileReference](),
36+
reflect.TypeFor[projectconfig.Origin](),
37+
}
38+
39+
// Maps "StructName.FieldName" for every field that should carry a
40+
// `fingerprint:"-"` tag. Catches accidental tag removal.
41+
//
42+
// Each entry documents WHY the field is excluded from the fingerprint:
43+
expectedExclusions := map[string]bool{
44+
// ComponentConfig.Name — metadata, already the map key in project config.
45+
"ComponentConfig.Name": true,
46+
// ComponentConfig.SourceConfigFile — internal bookkeeping reference, not a build input.
47+
"ComponentConfig.SourceConfigFile": true,
48+
49+
// ComponentBuildConfig.Failure — CI policy (expected failure tracking), not a build input.
50+
"ComponentBuildConfig.Failure": true,
51+
// ComponentBuildConfig.Hints — scheduling hints (e.g. expensive), not a build input.
52+
"ComponentBuildConfig.Hints": true,
53+
54+
// CheckConfig.SkipReason — human documentation for why check is skipped, not a build input.
55+
"CheckConfig.SkipReason": true,
56+
57+
// ComponentBuildFailureConfig — entire struct excluded via parent, but individual
58+
// fields are also tagged so reflection on the struct alone is consistent.
59+
// ComponentBuildFailureConfig.Expected — CI decision about expected failures.
60+
"ComponentBuildFailureConfig.Expected": true,
61+
// ComponentBuildFailureConfig.ExpectedReason — documentation for expected failure.
62+
"ComponentBuildFailureConfig.ExpectedReason": true,
63+
64+
// ComponentBuildHints — entire struct excluded via parent, fields also tagged.
65+
// ComponentBuildHints.Expensive — scheduling hint, does not affect build output.
66+
"ComponentBuildHints.Expensive": true,
67+
68+
// ComponentOverlay.Description — human-readable documentation for the overlay.
69+
"ComponentOverlay.Description": true,
70+
71+
// SourceFileReference.Component — back-reference to parent, not a build input.
72+
"SourceFileReference.Component": true,
73+
74+
// DistroReference.Snapshot — snapshot timestamp is not a build input; the resolved
75+
// upstream commit hash (captured separately via SourceIdentity) is what matters.
76+
// Excluding this prevents a snapshot bump from marking all upstream components as changed.
77+
"DistroReference.Snapshot": true,
78+
79+
// SourceFileReference.Origin — download location metadata (URI, type), not a build input.
80+
// The file content is already captured by Filename + Hash; changing a CDN URL should not
81+
// trigger a rebuild.
82+
"SourceFileReference.Origin": true,
83+
}
84+
85+
// Collect all actual exclusions found via reflection.
86+
actualExclusions := make(map[string]bool)
87+
88+
for _, st := range fingerprintedStructs {
89+
for i := range st.NumField() {
90+
field := st.Field(i)
91+
key := st.Name() + "." + field.Name
92+
93+
tag := field.Tag.Get("fingerprint")
94+
if tag == "-" {
95+
actualExclusions[key] = true
96+
}
97+
}
98+
}
99+
100+
// Verify every expected exclusion is actually present.
101+
for key := range expectedExclusions {
102+
assert.Truef(t, actualExclusions[key],
103+
"expected field %q to have `fingerprint:\"-\"` tag, but it does not — "+
104+
"was the tag accidentally removed?", key)
105+
}
106+
107+
// Verify no unexpected exclusions exist.
108+
for key := range actualExclusions {
109+
assert.Truef(t, expectedExclusions[key],
110+
"field %q has `fingerprint:\"-\"` tag but is not in expectedExclusions — "+
111+
"add it to expectedExclusions if the exclusion is intentional", key)
112+
}
113+
}

internal/projectconfig/overlay.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ type ComponentOverlay struct {
1717
// The type of overlay to apply.
1818
Type ComponentOverlayType `toml:"type" json:"type" validate:"required" jsonschema:"enum=spec-add-tag,enum=spec-insert-tag,enum=spec-set-tag,enum=spec-update-tag,enum=spec-remove-tag,enum=spec-prepend-lines,enum=spec-append-lines,enum=spec-search-replace,enum=patch-add,enum=patch-remove,enum=file-prepend-lines,enum=file-search-replace,enum=file-add,enum=file-remove,enum=file-rename,title=Overlay type,description=The type of overlay to apply"`
1919
// Human readable description of overlay; primarily present to document the need for the change.
20-
Description string `toml:"description,omitempty" json:"description,omitempty" jsonschema:"title=Description,description=Human readable description of overlay"`
20+
Description string `toml:"description,omitempty" json:"description,omitempty" jsonschema:"title=Description,description=Human readable description of overlay" fingerprint:"-"`
2121

2222
// For overlays that apply to non-spec files, indicates the filename. For overlays that can
2323
// apply to multiple files, supports glob patterns (including globstar).

0 commit comments

Comments
 (0)