-
Notifications
You must be signed in to change notification settings - Fork 126
Expand file tree
/
Copy pathHelmComponentDetector.cs
More file actions
205 lines (177 loc) · 7.13 KB
/
HelmComponentDetector.cs
File metadata and controls
205 lines (177 loc) · 7.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
#nullable disable
namespace Microsoft.ComponentDetection.Detectors.Helm;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.ComponentDetection.Common;
using Microsoft.ComponentDetection.Contracts;
using Microsoft.ComponentDetection.Contracts.Internal;
using Microsoft.ComponentDetection.Contracts.TypedComponent;
using Microsoft.Extensions.Logging;
using YamlDotNet.RepresentationModel;
public class HelmComponentDetector : FileComponentDetector, IDefaultOffComponentDetector
{
public HelmComponentDetector(
IComponentStreamEnumerableFactory componentStreamEnumerableFactory,
IObservableDirectoryWalkerFactory walkerFactory,
ILogger<HelmComponentDetector> logger)
{
this.ComponentStreamEnumerableFactory = componentStreamEnumerableFactory;
this.Scanner = walkerFactory;
this.Logger = logger;
}
public override string Id => "Helm";
public override IList<string> SearchPatterns { get; } =
[
"Chart.yaml", "Chart.yml",
"chart.yaml", "chart.yml",
"*values*.yaml", "*values*.yml",
];
public override IEnumerable<ComponentType> SupportedComponentTypes => [ComponentType.DockerReference];
public override int Version => 1;
public override IEnumerable<string> Categories => [Enum.GetName(typeof(DetectorClass), DetectorClass.Helm)];
protected override async Task OnFileFoundAsync(ProcessRequest processRequest, IDictionary<string, string> detectorArgs, CancellationToken cancellationToken = default)
{
var singleFileComponentRecorder = processRequest.SingleFileComponentRecorder;
var file = processRequest.ComponentStream;
try
{
var fileName = Path.GetFileName(file.Location);
// Only process files that are likely to contain image references. This is a heuristic to avoid parsing irrelevant YAML files.
if (!fileName.Contains("values", StringComparison.OrdinalIgnoreCase) ||
!(fileName.EndsWith(".yaml", StringComparison.OrdinalIgnoreCase) || fileName.EndsWith(".yml", StringComparison.OrdinalIgnoreCase)))
{
return;
}
this.Logger.LogInformation("Discovered Helm values file: {Location}", file.Location);
string contents;
using (var reader = new StreamReader(file.Stream))
{
contents = await reader.ReadToEndAsync(cancellationToken);
}
var yaml = new YamlStream();
yaml.Load(new StringReader(contents));
if (yaml.Documents.Count == 0)
{
return;
}
this.ExtractImageReferencesFromValues(yaml, singleFileComponentRecorder, file.Location);
}
catch (Exception e)
{
this.Logger.LogError(e, "Failed to parse Helm file: {Location}", file.Location);
}
}
private void ExtractImageReferencesFromValues(YamlStream yaml, ISingleFileComponentRecorder recorder, string fileLocation)
{
foreach (var document in yaml.Documents)
{
if (document.RootNode is YamlMappingNode rootMapping)
{
this.WalkYamlForImages(rootMapping, recorder, fileLocation);
}
}
}
/// <summary>
/// Walks the YAML tree looking for image references. Handles two common patterns:
/// 1. Direct image string: `image: nginx:1.21`
/// 2. Structured image object: `image: { repository: nginx, tag: "1.21" }`.
/// </summary>
private void WalkYamlForImages(YamlMappingNode mapping, ISingleFileComponentRecorder recorder, string fileLocation)
{
foreach (var entry in mapping.Children)
{
var key = (entry.Key as YamlScalarNode)?.Value;
if (string.Equals(key, "image", StringComparison.OrdinalIgnoreCase))
{
switch (entry.Value)
{
// image: nginx:1.21
case YamlScalarNode scalarValue when !string.IsNullOrWhiteSpace(scalarValue.Value):
this.TryRegisterImageReference(scalarValue.Value, recorder, fileLocation);
break;
// image:
// repository: nginx
// tag: "1.21"
case YamlMappingNode imageMapping:
this.TryRegisterStructuredImageReference(imageMapping, recorder, fileLocation);
break;
}
}
else if (entry.Value is YamlMappingNode childMapping)
{
this.WalkYamlForImages(childMapping, recorder, fileLocation);
}
else if (entry.Value is YamlSequenceNode sequenceNode)
{
foreach (var item in sequenceNode)
{
if (item is YamlMappingNode sequenceMapping)
{
this.WalkYamlForImages(sequenceMapping, recorder, fileLocation);
}
}
}
}
}
private void TryRegisterStructuredImageReference(YamlMappingNode imageMapping, ISingleFileComponentRecorder recorder, string fileLocation)
{
string repository = null;
string tag = null;
string digest = null;
string registry = null;
foreach (var child in imageMapping.Children)
{
var childKey = (child.Key as YamlScalarNode)?.Value;
var childValue = (child.Value as YamlScalarNode)?.Value;
switch (childKey?.ToLowerInvariant())
{
case "repository":
repository = childValue;
break;
case "tag":
tag = childValue;
break;
case "digest":
digest = childValue;
break;
case "registry":
registry = childValue;
break;
}
}
if (string.IsNullOrWhiteSpace(repository))
{
return;
}
var imageRef = !string.IsNullOrWhiteSpace(registry)
? $"{registry}/{repository}"
: repository;
if (!string.IsNullOrWhiteSpace(tag))
{
imageRef = $"{imageRef}:{tag}";
}
if (!string.IsNullOrWhiteSpace(digest))
{
imageRef = $"{imageRef}@{digest}";
}
this.TryRegisterImageReference(imageRef, recorder, fileLocation);
}
private void TryRegisterImageReference(string imageReference, ISingleFileComponentRecorder recorder, string fileLocation)
{
try
{
var dockerRef = DockerReferenceUtility.ParseFamiliarName(imageReference);
if (dockerRef != null)
{
recorder.RegisterUsage(new DetectedComponent(dockerRef.ToTypedDockerReferenceComponent()));
}
}
catch (Exception e)
{
this.Logger.LogWarning(e, "Failed to parse image reference '{ImageReference}' in {Location}", imageReference, fileLocation);
}
}
}