Skip to content

Commit 67bb639

Browse files
authored
Add Jenkins happy-path logging (#3908)
This source doesn't log very much, so if it doesn't scan what you expect, it's hard to figure out why. This PR adds some logging.
1 parent 881c2f9 commit 67bb639

File tree

1 file changed

+64
-24
lines changed

1 file changed

+64
-24
lines changed

pkg/sources/jenkins/jenkins.go

Lines changed: 64 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"time"
1212

1313
"github.com/go-errors/errors"
14-
"github.com/go-logr/logr"
1514
"github.com/trufflesecurity/trufflehog/v3/pkg/log"
1615
"google.golang.org/protobuf/proto"
1716
"google.golang.org/protobuf/types/known/anypb"
@@ -37,7 +36,6 @@ type Source struct {
3736
user string
3837
token string
3938
header *header
40-
log logr.Logger
4139
client *http.Client
4240
sources.Progress
4341
}
@@ -66,8 +64,6 @@ func (s *Source) JobID() sources.JobID {
6664

6765
// Init returns an initialized Jenkins source.
6866
func (s *Source) Init(aCtx context.Context, name string, jobId sources.JobID, sourceId sources.SourceID, verify bool, connection *anypb.Any, _ int) error {
69-
s.log = aCtx.Logger()
70-
7167
s.name = name
7268
s.sourceId = sourceId
7369
s.jobId = jobId
@@ -89,7 +85,7 @@ func (s *Source) Init(aCtx context.Context, name string, jobId sources.JobID, so
8985

9086
const retryDelay = time.Second * 30
9187
opts = append(opts,
92-
roundtripper.WithLogger(s.log),
88+
roundtripper.WithLogger(aCtx.Logger()),
9389
roundtripper.WithLogging(),
9490
roundtripper.WithRetryable(
9591
roundtripper.WithShouldRetry5XXDuration(retryDelay),
@@ -104,6 +100,7 @@ func (s *Source) Init(aCtx context.Context, name string, jobId sources.JobID, so
104100
s.client = client
105101

106102
var unparsedURL string
103+
var authMethod string
107104
switch cred := conn.GetCredential().(type) {
108105
case *sourcespb.Jenkins_BasicAuth:
109106
unparsedURL = conn.Endpoint
@@ -113,15 +110,18 @@ func (s *Source) Init(aCtx context.Context, name string, jobId sources.JobID, so
113110
return errors.Errorf("Jenkins source basic auth credential requires 'password' to be specified")
114111
}
115112
log.RedactGlobally(s.token)
113+
authMethod = "basic"
116114
case *sourcespb.Jenkins_Header:
117115
unparsedURL = conn.Endpoint
118116
s.header = &header{
119117
key: cred.Header.Key,
120118
value: cred.Header.Value,
121119
}
122120
log.RedactGlobally(cred.Header.GetValue())
121+
authMethod = "header"
123122
case *sourcespb.Jenkins_Unauthenticated:
124123
unparsedURL = conn.Endpoint
124+
authMethod = "none"
125125
default:
126126
return errors.Errorf("unknown or unspecified authentication method provided for Jenkins source %q (unauthenticated scans must be explicitly configured)", name)
127127
}
@@ -131,6 +131,11 @@ func (s *Source) Init(aCtx context.Context, name string, jobId sources.JobID, so
131131
return errors.WrapPrefix(err, fmt.Sprintf("Invalid endpoint URL given for Jenkins source: %s", unparsedURL), 0)
132132
}
133133

134+
aCtx.Logger().V(1).Info("initialized Jenkins source",
135+
"auth_method", authMethod,
136+
"url_raw", unparsedURL,
137+
"url_parsed", s.url.String())
138+
134139
return nil
135140
}
136141

@@ -187,13 +192,24 @@ func (s *Source) GetJenkinsJobs(ctx context.Context) (JenkinsJobResponse, error)
187192
}
188193

189194
func (s *Source) RecursivelyGetJenkinsObjectsForPath(ctx context.Context, absolutePath string) (JenkinsJobResponse, error) {
195+
ctx.Logger().V(3).Info("getting objects",
196+
"path", absolutePath)
197+
190198
jobs := JenkinsJobResponse{}
191199
objects, err := s.GetJenkinsObjectsForPath(ctx, absolutePath)
192200
if err != nil {
193201
return jobs, err
194202
}
203+
ctx.Logger().V(3).Info("got objects",
204+
"path", absolutePath,
205+
"count", len(objects.Jobs))
195206

196207
for _, job := range objects.Jobs {
208+
ctx.Logger().V(3).Info("processing object",
209+
"object_name", job.Name,
210+
"object_class", job.Class,
211+
"object_url", job.Url)
212+
197213
if job.Class == "com.cloudbees.hudson.plugins.folder.Folder" {
198214
u, err := url.Parse(job.Url)
199215
if err != nil {
@@ -221,6 +237,7 @@ func (s *Source) GetJenkinsObjectsForPath(ctx context.Context, absolutePath stri
221237
baseUrl.Path = path.Join(absolutePath, "api/json")
222238
baseUrl.RawQuery = fmt.Sprintf("tree=jobs[name,url]{%d,%d}", i, i+100)
223239

240+
ctx.Logger().V(4).Info("executing query", "query_url", baseUrl.String())
224241
req, err := s.NewRequest(http.MethodGet, baseUrl.String(), nil)
225242
if err != nil {
226243
return res, errors.WrapPrefix(err, "Failed to create new request to get jenkins jobs", 0)
@@ -255,6 +272,11 @@ func (s *Source) GetJenkinsObjectsForPath(ctx context.Context, absolutePath stri
255272
}
256273

257274
func (s *Source) GetJenkinsBuilds(ctx context.Context, jobAbsolutePath string) (JenkinsBuildResponse, error) {
275+
ctx = context.WithValues(ctx,
276+
"job_path", jobAbsolutePath)
277+
278+
ctx.Logger().V(2).Info("getting builds")
279+
258280
builds := JenkinsBuildResponse{}
259281
buildsUrl := *s.url
260282
for i := 0; true; i += 100 {
@@ -265,6 +287,7 @@ func (s *Source) GetJenkinsBuilds(ctx context.Context, jobAbsolutePath string) (
265287
return builds, errors.WrapPrefix(err, "Failed to create new request to get jenkins builds", 0)
266288
}
267289

290+
ctx.Logger().V(4).Info("executing query", "query_url", req.URL.String())
268291
resp, err := s.client.Do(req)
269292
if err != nil {
270293
return builds, errors.WrapPrefix(err, "Failed to do get jenkins builds request", 0)
@@ -299,34 +322,48 @@ func (s *Source) Chunks(ctx context.Context, chunksChan chan *sources.Chunk, _ .
299322
if err != nil {
300323
return errors.WrapPrefix(err, "Failed to get Jenkins job response", 0)
301324
}
325+
ctx.Logger().V(1).Info("got jobs", "count", len(jobs.Jobs))
302326

303327
for i, project := range jobs.Jobs {
304328
if common.IsDone(ctx) {
305329
return nil
306330
}
307331

332+
ctx := context.WithValues(ctx,
333+
"job_name", project.Name,
334+
"job_class", project.Class,
335+
"job_url", project.Url)
336+
308337
s.SetProgressComplete(i, len(jobs.Jobs), fmt.Sprintf("Project: %s", project.Name), "")
309338

310339
parsedUrl, err := url.Parse(project.Url)
311340
if err != nil {
312-
s.log.Error(err, "Failed to parse Jenkins project URL, skipping project", "url", project.Url, "project", project.Name)
341+
ctx.Logger().Error(err, "failed to parse job URL; skipping job")
313342
continue
314343
}
315344
projectURL := *s.url
316345
projectURL.Path = parsedUrl.Path
317346

318347
builds, err := s.GetJenkinsBuilds(ctx, projectURL.Path)
319348
if err != nil {
320-
s.log.Error(err, "Failed to get Jenkins build response, skipping project", "project", project.Name)
349+
ctx.Logger().Error(err, "failed to get builds; skipping job")
321350
continue
322351
}
352+
ctx.Logger().V(2).Info("got builds",
353+
"count", len(builds.Builds))
323354

324355
for _, build := range builds.Builds {
325356
if common.IsDone(ctx) {
326357
return nil
327358
}
328359

329-
s.chunkBuild(ctx, build, project.Name, chunksChan)
360+
ctx := context.WithValues(ctx,
361+
"build_number", build.Number,
362+
"build_url", build.Url)
363+
364+
if err := s.chunkBuild(ctx, build, project.Name, chunksChan); err != nil {
365+
ctx.Logger().Error(err, "error scanning build log")
366+
}
330367
}
331368
}
332369

@@ -336,45 +373,46 @@ func (s *Source) Chunks(ctx context.Context, chunksChan chan *sources.Chunk, _ .
336373

337374
// chunkBuild takes build information and sends it to the chunksChan.
338375
// It also logs all errors that occur and does not return them, as the parent context expects to continue running.
339-
func (s *Source) chunkBuild(_ context.Context, build JenkinsBuild, projectName string, chunksChan chan *sources.Chunk) {
340-
// Setup a logger to identify the build and project.
341-
chunkBuildLog := s.log.WithValues(
342-
"build", build.Number,
343-
"project", projectName,
344-
)
376+
func (s *Source) chunkBuild(
377+
ctx context.Context,
378+
build JenkinsBuild,
379+
projectName string,
380+
chunksChan chan *sources.Chunk,
381+
) error {
382+
ctx.Logger().V(2).Info("chunking build")
345383

346384
parsedUrl, err := url.Parse(build.Url)
347385
if err != nil {
348-
chunkBuildLog.Error(err, "Failed to parse Jenkins build URL, skipping build", "url", build.Url)
349-
return
386+
return fmt.Errorf("failed to parse build URL %q: %w", build.Url, err)
350387
}
351388
buildLogURL := *s.url
352389
buildLogURL.Path = path.Join(parsedUrl.Path, "consoleText")
390+
ctx = context.WithValues(ctx,
391+
"build_log_url", buildLogURL.String())
353392

354393
req, err := s.NewRequest(http.MethodGet, buildLogURL.String(), nil)
355394
if err != nil {
356-
chunkBuildLog.Error(err, "Failed to create new request to Jenkins, skipping build")
357-
return
395+
return fmt.Errorf("failed to create HTTP request to %q: %w", buildLogURL.String(), err)
358396
}
359397

360398
resp, err := s.client.Do(req)
361399
if err != nil {
362-
chunkBuildLog.Error(err, "Failed to get build log in Jenkins chunks, skipping build")
363-
return
400+
return fmt.Errorf("could not retrieve build log from %q: %w", buildLogURL.String(), err)
364401
}
365402
defer resp.Body.Close()
366403

367404
if resp.StatusCode >= 400 {
368-
chunkBuildLog.Error(err, "Status Code from build was unexpected, skipping build", "status_code", resp.StatusCode)
369-
return
405+
return fmt.Errorf("got unexpected HTTP status code %v when trying to retrieve build log from %q",
406+
resp.StatusCode,
407+
buildLogURL.String())
370408
}
371409

372410
buildLog, err := io.ReadAll(resp.Body)
373411
if err != nil {
374-
chunkBuildLog.Error(err, "Failed to read body from the build log response, skipping build")
375-
return
412+
return fmt.Errorf("error reading build log response body from %q: %w", buildLogURL.String(), err)
376413
}
377414

415+
ctx.Logger().V(4).Info("scanning build log")
378416
chunksChan <- &sources.Chunk{
379417
SourceName: s.name,
380418
SourceID: s.SourceID(),
@@ -392,6 +430,8 @@ func (s *Source) chunkBuild(_ context.Context, build JenkinsBuild, projectName s
392430
Data: buildLog,
393431
Verify: s.verify,
394432
}
433+
434+
return nil
395435
}
396436

397437
type JenkinsJobResponse struct {

0 commit comments

Comments
 (0)