• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

openshift-kni / lifecycle-agent / 567839dabd85027fcc379712ab73d3ce3dd9d8a8

20 May 2026 04:08PM UTC coverage: 30.271% (+0.5%) from 29.8%
567839dabd85027fcc379712ab73d3ce3dd9d8a8

push

web-flow
Merge pull request #6318 from sebrandon1/CNF-23417

CNF-23417: Add unit tests for rollback handlers

4920 of 16253 relevant lines covered (30.27%)

0.35 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/internal/precache/workload/pullImages.go
1
/*
2
 * Copyright 2023 Red Hat, Inc.
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this inputFilePath 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

17
package workload
18

19
import (
20
        "fmt"
21
        "os"
22
        "strconv"
23
        "strings"
24
        "sync"
25

26
        log "github.com/sirupsen/logrus"
27

28
        "github.com/openshift-kni/lifecycle-agent/internal/precache"
29
        "github.com/openshift-kni/lifecycle-agent/lca-cli/ops"
30
)
31

32
// MaxRetries is the max number of retries for pulling an image before marking it as failed
33
const MaxRetries int = 5
34

35
// Podman auth-file related constants
36
const (
37
        EnvAuthFile     string = "PULL_SECRET_PATH"
38
        DefaultAuthFile string = "/var/lib/kubelet/config.json"
39
)
40

41
var (
42
        logExec = &log.Logger{
43
                Level: log.ErrorLevel, // reducing log level and only report if the exec calls fail
44
        }
45
        Executor = ops.NewRegularExecutor(logExec, false)
46
)
47

48
// CheckPodman verifies that podman is running by checking the version of podman
49
func CheckPodman() bool {
×
50
        if _, err := Executor.ExecuteWithLiveLogger("podman", []string{"version"}...); err != nil {
×
51
                return false
×
52
        }
×
53
        return true
×
54
}
55

56
// podmanImgPull pulls the specified image via podman CLI
57
func podmanImgPull(image, authFile string) error {
×
58
        args := []string{"pull", image}
×
59
        if authFile != "" {
×
60
                args = append(args, []string{"--authfile", authFile}...)
×
61
        }
×
62
        if _, err := Executor.Execute("podman", args...); err != nil {
×
63
                return fmt.Errorf("failed podman pull with args %s: %w", args, err)
×
64
        }
×
65
        return nil
×
66
}
67

68
func podmanImgExists(image string) bool {
×
69
        args := []string{"image", "exists", image}
×
70
        _, err := Executor.Execute("podman", args...)
×
71
        if err != nil {
×
72
                log.Errorf("failed podman image check with args %s: %v", args, err)
×
73
        }
×
74
        return err == nil
×
75
}
76

77
// pullImage attempts to pull an image via podman CLI
78
func pullImage(image, authFile string, progress *precache.Progress) error {
×
79

×
80
        var err error
×
81
        for i := 0; i < MaxRetries; i++ {
×
82
                err = podmanImgPull(image, authFile)
×
83
                if err == nil {
×
84
                        log.Infof("Successfully pulled image: %s", image)
×
85
                        break
×
86
                } else {
×
87
                        message := fmt.Sprintf("%v", err)
×
88
                        log.Infof("Attempt %d/%d: Failed to pull %s: %s", i+1, MaxRetries, image, message)
×
89
                        if strings.Contains(message, "manifest unknown") {
×
90
                                // no point to retry if we can reach the registry and the digest doesn't exist
×
91
                                break
×
92
                        }
93
                }
94
        }
95
        // update precache progress tracker
96
        progress.Update(err == nil, image)
×
97

×
98
        // persist progress to file
×
99
        progress.Persist(precache.StatusFile)
×
100

×
101
        return err
×
102
}
103

104
// getAuthFile returns the auth file for podman
105
func GetAuthFile() (string, error) {
×
106
        // Configure Podman auth file
×
107
        authFile := os.Getenv(EnvAuthFile)
×
108
        if authFile == "" {
×
109
                authFile = DefaultAuthFile
×
110
        }
×
111

112
        // Check if authFile exists
113
        if _, err := os.Stat(authFile); os.IsNotExist(err) { //nolint:gosec // authFile path is validated
×
114
                return "", fmt.Errorf("failed to get authfile for podman: %w", err)
×
115
        }
×
116
        log.Info("Auth file for podman found.")
×
117

×
118
        return authFile, nil
×
119
}
120

121
// PullImages pulls a list of images using podman
122
func PullImages(precacheSpec []string, authFile string) *precache.Progress {
×
123

×
124
        // Initialize progress tracking
×
125
        progress := &precache.Progress{
×
126
                Total:  len(precacheSpec),
×
127
                Pulled: 0,
×
128
                Failed: 0,
×
129
        }
×
130

×
131
        log.Infof("Will attempt to pull %d images", len(precacheSpec))
×
132

×
133
        // Create wait group and pull images
×
134
        var wg sync.WaitGroup
×
135
        numThreads, err := strconv.Atoi(os.Getenv(precache.EnvMaxPullThreads))
×
136
        if err != nil {
×
137
                numThreads = precache.DefaultMaxConcurrentPulls
×
138
        }
×
139
        threads := make(chan struct{}, numThreads)
×
140
        log.Infof("Configured precaching job to concurrently pull %d images.", numThreads)
×
141

×
142
        // Start pulling images
×
143
        for _, image := range precacheSpec {
×
144
                threads <- struct{}{}
×
145
                wg.Add(1)
×
146
                go func(image string) {
×
147
                        defer func() {
×
148
                                <-threads
×
149
                                wg.Done()
×
150
                        }()
×
151
                        err := pullImage(image, authFile, progress)
×
152

×
153
                        if err != nil {
×
154
                                log.Errorf("Failed to pull image: %s, error: %v", image, err)
×
155
                        }
×
156
                }(image)
157
        }
158

159
        log.Info("Waiting for precaching threads to finish...")
×
160
        // Wait for all threads to complete
×
161
        wg.Wait()
×
162
        log.Info("All the precaching threads have finished.")
×
163

×
164
        // Log final progress
×
165
        progress.Log()
×
166

×
167
        // Store final precache progress report to file
×
168
        progress.Persist(precache.StatusFile)
×
169

×
170
        return progress
×
171
}
172

173
func ValidatePrecache(status *precache.Progress, bestEffort bool) error {
×
174
        // Check pre-caching execution status
×
175
        if status.Failed != 0 {
×
176
                var imagesFound []string
×
177
                log.Info("Failed to pre-cache the following images:")
×
178
                for _, image := range status.FailedPullList {
×
179
                        if podmanImgExists(image) {
×
180
                                log.Infof("%s, but found locally after downloading other images", image)
×
181
                                imagesFound = append(imagesFound, image)
×
182
                        } else {
×
183
                                log.Info(image)
×
184
                        }
×
185
                }
186
                // return success if the number of images found matches the fail count
187
                if len(imagesFound) == status.Failed {
×
188
                        return nil
×
189
                }
×
190
                if bestEffort {
×
191
                        log.Info("Failed to precache, running in best-effort mode, skip error")
×
192
                        return nil
×
193
                }
×
194
                return fmt.Errorf("failed to pre-cache one or more images")
×
195
        }
196
        return nil
×
197
}
198

199
func Precache(precacheSpec []string, authFile string, bestEffort bool) error {
×
200
        // Pre-cache images
×
201
        status := PullImages(precacheSpec, authFile)
×
202
        log.Info("Completed executing pre-caching")
×
203

×
204
        if err := ValidatePrecache(status, bestEffort); err != nil {
×
205
                return fmt.Errorf("failed to pre-cache one or more images")
×
206
        }
×
207

208
        log.Info("Pre-cached images successfully.")
×
209
        return nil
×
210
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc