1+ /*
2+ * Copyright (c) 2024. Devtron Inc.
3+ *
4+ * Licensed under the Apache License, Version 2.0 (the "License");
5+ * you may not use this file except in compliance with the License.
6+ * You may obtain a copy of the License at
7+ *
8+ * http://www.apache.org/licenses/LICENSE-2.0
9+ *
10+ * Unless required by applicable law or agreed to in writing, software
11+ * distributed under the License is distributed on an "AS IS" BASIS,
12+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+ * See the License for the specific language governing permissions and
14+ * limitations under the License.
15+ */
16+
117package helper
218
319import (
@@ -11,36 +27,16 @@ import (
1127 "time"
1228
1329 "github.com/caarlos0/env"
30+ "github.com/devtron-labs/ci-runner/helper/bean"
1431 "github.com/devtron-labs/ci-runner/util"
1532 "github.com/go-resty/resty/v2"
1633)
1734
18- // DockerfileScanRequest represents the request to scan a Dockerfile
19- type DockerfileScanRequest struct {
20- AppId int `json:"appId"`
21- BuildId int `json:"buildId"`
22- PipelineId int `json:"pipelineId"`
23- DockerfileContent string `json:"dockerfileContent"`
24- DockerfileScanEnabled bool `json:"dockerfileScanEnabled"`
25- ForceDockerfileScan bool `json:"forceDockerfileScan"`
26- IgnoredRules []string `json:"ignoredRules"`
27- }
28-
29- // ScanConfig holds configuration for Dockerfile scanning
30- type ScanConfig struct {
31- ImageScannerEndpoint string `env:"IMAGE_SCANNER_ENDPOINT" envDefault:"http://image-scanner-service.devtroncd:80"`
32- FailOnError bool `env:"DOCKERFILE_SCAN_FAIL_ON_ERROR" envDefault:"false"`
33- MaxRetries int `env:"DOCKERFILE_SCAN_MAX_RETRIES" envDefault:"3"`
34- RetryWaitTimeSeconds int `env:"DOCKERFILE_SCAN_RETRY_WAIT_SECONDS" envDefault:"5"`
35- }
36-
3735// MaxDockerfileSize is the maximum allowed Dockerfile size (1MB)
3836const MaxDockerfileSize = 1 * 1024 * 1024 // 1MB
3937
4038// InitiateDockerfileScan initiates a Dockerfile scan using hadolint
4139// It reads the Dockerfile from filesystem and sends content to image-scanner service
42- // Note: The decision to run the scan is made by the caller (runBuildArtifact)
43- // which handles FORCE_DOCKERFILE_SCAN flag and pipeline-level DockerfileScanEnabled settings
4440func InitiateDockerfileScan (ciRequest * CommonWorkflowRequest ) {
4541 log .Println (util .DEVTRON , "initiating Dockerfile scan" )
4642
@@ -50,83 +46,62 @@ func InitiateDockerfileScan(ciRequest *CommonWorkflowRequest) {
5046 return
5147 }
5248
53- // Wait for git clone to complete (checkout path to exist)
54- // Use the SAME path resolution as Docker build (getDockerfilePath)
49+ // Resolve Dockerfile path
5550 var dockerfilePath string
5651 if ciRequest .CiBuildConfig .CiBuildType == "managed-dockerfile-build" {
57- // For managed Dockerfile, use GetSelfManagedDockerfilePath
5852 dockerfilePath = filepath .Join (util .WORKINGDIR , ciRequest .CheckoutPath , "./Dockerfile" )
5953 } else {
60- // For self-managed Dockerfile, use the configured path
6154 dockerfilePath = ciRequest .CiBuildConfig .DockerBuildConfig .DockerfilePath
6255 }
63- // Convert to absolute path (same as Docker build)
64- dockerfilePath , _ = filepath .Abs (dockerfilePath )
6556
66- // Fallback wait (should not be needed - scan triggered at Docker build start)
67- maxWait := 2 * time .Minute
68- waitInterval := 10 * time .Second
69- startTime := time .Now ()
70-
71- log .Println (util .DEVTRON , "dockerfile scan: waiting for git clone to complete" , "path" , dockerfilePath , "buildId" , ciRequest .WorkflowId )
57+ // Convert to absolute path
58+ var err error
59+ dockerfilePath , err = filepath .Abs (dockerfilePath )
60+ if err != nil {
61+ log .Println (util .DEVTRON , "error in resolving absolute path for Dockerfile" , "path" , dockerfilePath , "err" , err )
62+ return
63+ }
7264
73- for time .Since (startTime ) < maxWait {
74- if _ , err := os .Stat (dockerfilePath ); err == nil {
75- log .Println (util .DEVTRON , "dockerfile scan: Dockerfile found, proceeding" , "path" , dockerfilePath , "elapsed" , time .Since (startTime ).Round (time .Second ))
76- break // File exists, proceed
77- }
78- // Log progress every 30 seconds
79- if int (time .Since (startTime ).Seconds ())% 30 == 0 {
80- log .Println (util .DEVTRON , "dockerfile scan: waiting for git clone to complete..." , "path" , dockerfilePath , "elapsed" , time .Since (startTime ).Round (time .Second ), "maxWait" , maxWait )
81- }
82- time .Sleep (waitInterval )
65+ // Check if Dockerfile exists (removed polling logic)
66+ if _ , err := os .Stat (dockerfilePath ); os .IsNotExist (err ) {
67+ log .Println (util .DEVTRON , "dockerfile scan: Dockerfile not found at" , dockerfilePath )
68+ return
8369 }
8470
85- // Read Dockerfile from filesystem (single source of truth)
71+ // Read Dockerfile from filesystem
8672 dockerfileContent , err := os .ReadFile (dockerfilePath )
8773 if err != nil {
88- log .Println (util .DEVTRON , "error in reading Dockerfile for scanning" ,
89- "path" , dockerfilePath , "err" , err )
90- if err := handleScanError (fmt .Sprintf ("Failed to read Dockerfile from %s: %v" , dockerfilePath , err ), ciRequest .DockerfileScanEnabled ); err != nil {
91- return
92- }
74+ log .Println (util .DEVTRON , "error in reading Dockerfile for scanning" , "path" , dockerfilePath , "err" , err )
75+ handleScanError (fmt .Sprintf ("Failed to read Dockerfile: %v" , err ), ciRequest .DockerfileScanEnabled )
9376 return
9477 }
9578
96- // Prepare scan request with Dockerfile content
97- // CRITICAL FIX: Read DockerfileScanEnabled from ciRequest.DockerfileScanEnabled (CommonWorkflowRequest level)
98- // NOT from ciRequest.CiBuildConfig.DockerBuildConfig.DockerfileScanEnabled (which may be out of sync)
99- // ForceDockerfileScan is only available at CommonWorkflowRequest level
100- scanRequest := & DockerfileScanRequest {
79+ // Prepare scan request
80+ scanRequest := & bean.DockerfileScanRequest {
10181 AppId : ciRequest .AppId ,
10282 BuildId : ciRequest .WorkflowId ,
10383 PipelineId : ciRequest .PipelineId ,
10484 DockerfileContent : string (dockerfileContent ),
10585 DockerfileScanEnabled : ciRequest .DockerfileScanEnabled ,
10686 ForceDockerfileScan : ciRequest .ForceDockerfileScan ,
107- IgnoredRules : []string {}, // Can be populated from config in future
10887 }
10988
11089 jsonBody , err := json .Marshal (scanRequest )
11190 if err != nil {
11291 log .Println (util .DEVTRON , "error in marshalling Dockerfile scan request" , "err" , err )
113- if err := handleScanError (fmt .Sprintf ("Failed to marshal scan request: %v" , err ), ciRequest .DockerfileScanEnabled ); err != nil {
114- return
115- }
92+ handleScanError (fmt .Sprintf ("Failed to marshal scan request: %v" , err ), ciRequest .DockerfileScanEnabled )
11693 return
11794 }
11895
119- cfg := & ScanConfig {}
96+ cfg := & bean. ScanConfig {}
12097 err = env .Parse (cfg )
12198 if err != nil {
12299 log .Println (util .DEVTRON , "error in parsing scan config" , "err" , err )
123- if err := handleScanError (fmt .Sprintf ("Failed to parse scan config: %v" , err ), ciRequest .DockerfileScanEnabled ); err != nil {
124- return
125- }
100+ handleScanError (fmt .Sprintf ("Failed to parse scan config: %v" , err ), ciRequest .DockerfileScanEnabled )
126101 return
127102 }
128103
129- // Create HTTP client with timeout and configurable retries
104+ // Create HTTP client (Using local instantiation but with improved error logging)
130105 client := resty .New ()
131106 client .SetTimeout (2 * time .Minute )
132107 client .
@@ -139,28 +114,28 @@ func InitiateDockerfileScan(ciRequest *CommonWorkflowRequest) {
139114 SetBody (jsonBody ).
140115 Post (fmt .Sprintf ("%s/%s" , cfg .ImageScannerEndpoint , "scanner/dockerfile/scan" ))
141116
142- // Record success/failure in circuit breaker
117+ // Record success/failure with actual error logging
143118 if err != nil || (resp != nil && (resp .StatusCode () != http .StatusAccepted && resp .StatusCode () != http .StatusOK )) {
144- log .Println (util .DEVTRON , "circuit breaker recorded FAILURE" , "buildId" , ciRequest .WorkflowId )
119+ var status string
120+ if resp != nil {
121+ status = resp .Status ()
122+ }
123+ log .Println (util .DEVTRON , "circuit breaker recorded FAILURE for Dockerfile scan" , "buildId" , ciRequest .WorkflowId , "err" , err , "status" , status )
145124 } else {
146- log .Println (util .DEVTRON , "circuit breaker recorded SUCCESS" , "buildId" , ciRequest .WorkflowId )
125+ log .Println (util .DEVTRON , "circuit breaker recorded SUCCESS for Dockerfile scan " , "buildId" , ciRequest .WorkflowId )
147126 }
148127
149128 if err != nil {
150129 log .Println (util .DEVTRON , "error in calling image-scanner for Dockerfile scan" , "err" , err )
151- if err := handleScanError (fmt .Sprintf ("Dockerfile scan failed: %v" , err ), cfg .FailOnError ); err != nil {
152- return
153- }
130+ handleScanError (fmt .Sprintf ("Dockerfile scan failed: %v" , err ), cfg .FailOnError )
154131 return
155132 }
156133
157- // Accept both 202 (Accepted) and 200 (OK - for cached results )
134+ // Accept both 202 (Accepted) and 200 (OK)
158135 if resp .StatusCode () != http .StatusAccepted && resp .StatusCode () != http .StatusOK {
159136 log .Println (util .DEVTRON , "image-scanner returned non-202/200 status for Dockerfile scan" ,
160137 "status" , resp .StatusCode (), "body" , string (resp .Body ()))
161- if err := handleScanError (fmt .Sprintf ("Dockerfile scan failed with status: %d" , resp .StatusCode ()), cfg .FailOnError ); err != nil {
162- return
163- }
138+ handleScanError (fmt .Sprintf ("Dockerfile scan failed with status: %d" , resp .StatusCode ()), cfg .FailOnError )
164139 return
165140 }
166141
@@ -169,12 +144,11 @@ func InitiateDockerfileScan(ciRequest *CommonWorkflowRequest) {
169144}
170145
171146// handleScanError handles scan errors based on FailOnError configuration
172- func handleScanError (message string , failOnError bool ) error {
147+ func handleScanError (message string , failOnError bool ) {
173148 if failOnError {
174149 log .Println (util .DEVTRON , "Dockerfile scan failed (fail-on-error enabled)" , "message" , message )
175- return fmt .Errorf ("Dockerfile scan failed: %s" , message )
150+ // We don't return error here because this is called in a goroutine
151+ } else {
152+ log .Println (util .DEVTRON , "Dockerfile scan failed (fail-on-error disabled)" , "message" , message )
176153 }
177- // Log warning but don't fail the build
178- log .Println (util .DEVTRON , "Dockerfile scan failed (fail-on-error disabled)" , "message" , message )
179- return nil
180154}
0 commit comments