Skip to content

Commit 6e63e35

Browse files
authored
feat: add --remote-repo option to image build (#427)
1 parent bf51454 commit 6e63e35

3 files changed

Lines changed: 407 additions & 21 deletions

File tree

internal/app/azldev/cmds/image/build.go

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ type ImageBuildOptions struct {
2424

2525
// Paths to local repositories to include during build.
2626
LocalRepoPaths []string
27+
28+
// URIs to remote repositories (http:// or https://) to include during build.
29+
RemoteRepoPaths []string
30+
31+
// NoRemoteRepoGpgCheck disables GPG checking for all remote repositories
32+
// specified via RemoteRepoPaths.
33+
NoRemoteRepoGpgCheck bool
2734
}
2835

2936
// ImageBuildResult summarizes the results of building an image.
@@ -66,6 +73,10 @@ This command invokes kiwi-ng via sudo to build the image.`,
6673

6774
cmd.Flags().StringArrayVar(&options.LocalRepoPaths, "local-repo", []string{},
6875
"Paths to local repositories to include during build (can be specified multiple times)")
76+
cmd.Flags().StringArrayVar(&options.RemoteRepoPaths, "remote-repo", []string{},
77+
"URIs to remote repositories (http:// or https://) to include during build (can be specified multiple times)")
78+
cmd.Flags().BoolVar(&options.NoRemoteRepoGpgCheck, "remote-repo-no-gpgcheck", false,
79+
"Disable GPG checking for all remote repositories specified via --remote-repo (not for production use)")
6980

7081
return cmd
7182
}
@@ -132,9 +143,9 @@ func BuildImage(env *azldev.Env, options *ImageBuildOptions) (*ImageBuildResult,
132143
}
133144

134145
// Run kiwi to build the image into the work directory.
135-
kiwiRunner := kiwi.NewRunner(env, filepath.Dir(kiwiDefPath)).WithTargetDir(kiwiWorkDir)
136-
for _, repoPath := range options.LocalRepoPaths {
137-
kiwiRunner.AddLocalRepo(repoPath)
146+
kiwiRunner, err := createKiwiRunner(env, kiwiDefPath, kiwiWorkDir, options)
147+
if err != nil {
148+
return nil, err
138149
}
139150

140151
err = kiwiRunner.Build(env)
@@ -167,6 +178,29 @@ func checkBuildPrerequisites(env *azldev.Env) error {
167178
return nil
168179
}
169180

181+
// createKiwiRunner sets up a kiwi runner with the configured repositories.
182+
func createKiwiRunner(
183+
env *azldev.Env,
184+
kiwiDefPath, targetDir string,
185+
options *ImageBuildOptions,
186+
) (*kiwi.Runner, error) {
187+
runner := kiwi.NewRunner(env, filepath.Dir(kiwiDefPath)).
188+
WithTargetDir(targetDir).
189+
WithRemoteRepoGPGCheck(!options.NoRemoteRepoGpgCheck)
190+
191+
for _, repoURI := range options.RemoteRepoPaths {
192+
if err := runner.AddRemoteRepo(repoURI); err != nil {
193+
return nil, fmt.Errorf("invalid remote repository:\n%w", err)
194+
}
195+
}
196+
197+
for _, repoPath := range options.LocalRepoPaths {
198+
runner.AddLocalRepo(repoPath)
199+
}
200+
201+
return runner, nil
202+
}
203+
170204
// linkImageArtifacts hard-links the final image artifacts from the work directory to the
171205
// output directory. Uses symlinks to avoid duplicating large image files.
172206
// It parses kiwi's result JSON to determine which files are artifacts.

internal/utils/kiwi/kiwi.go

Lines changed: 92 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"errors"
1111
"fmt"
1212
"log/slog"
13+
"net/url"
1314
"os"
1415
"os/exec"
1516
"path/filepath"
@@ -58,28 +59,38 @@ type Runner struct {
5859

5960
// localRepoPaths are paths to local RPM repositories to include during build.
6061
localRepoPaths []string
62+
63+
// remoteRepoPaths are URIs to remote RPM repositories (http:// or https://) to include during build.
64+
remoteRepoPaths []string
65+
66+
// remoteRepoGPGCheck controls whether GPG checking is enabled for remote repositories.
67+
// Defaults to true.
68+
remoteRepoGPGCheck bool
6169
}
6270

6371
// NewRunner constructs a new [Runner] that can be used to invoke kiwi-ng.
6472
// The descriptionDir is the directory containing the kiwi description file (e.g., config.xml).
6573
func NewRunner(ctx opctx.Ctx, descriptionDir string) *Runner {
6674
return &Runner{
67-
fs: ctx.FS(),
68-
cmdFactory: ctx,
69-
verbose: ctx.Verbose(),
70-
descriptionDir: descriptionDir,
75+
fs: ctx.FS(),
76+
cmdFactory: ctx,
77+
verbose: ctx.Verbose(),
78+
descriptionDir: descriptionDir,
79+
remoteRepoGPGCheck: true, // GPG checking enabled by default
7180
}
7281
}
7382

7483
// Clone creates a deep copy of the provided [Runner] instance.
7584
func (r *Runner) Clone() *Runner {
7685
return &Runner{
77-
fs: r.fs,
78-
cmdFactory: r.cmdFactory,
79-
verbose: r.verbose,
80-
descriptionDir: r.descriptionDir,
81-
targetDir: r.targetDir,
82-
localRepoPaths: deep.MustCopy(r.localRepoPaths),
86+
fs: r.fs,
87+
cmdFactory: r.cmdFactory,
88+
verbose: r.verbose,
89+
descriptionDir: r.descriptionDir,
90+
targetDir: r.targetDir,
91+
localRepoPaths: deep.MustCopy(r.localRepoPaths),
92+
remoteRepoPaths: deep.MustCopy(r.remoteRepoPaths),
93+
remoteRepoGPGCheck: r.remoteRepoGPGCheck,
8394
}
8495
}
8596

@@ -90,6 +101,19 @@ func (r *Runner) WithTargetDir(targetDir string) *Runner {
90101
return r
91102
}
92103

104+
// WithRemoteRepoGPGCheck sets whether GPG checking is enabled for remote repositories.
105+
// By default, GPG checking is enabled.
106+
func (r *Runner) WithRemoteRepoGPGCheck(enabled bool) *Runner {
107+
r.remoteRepoGPGCheck = enabled
108+
109+
return r
110+
}
111+
112+
// RemoteRepoGPGCheck returns whether GPG checking is enabled for remote repositories.
113+
func (r *Runner) RemoteRepoGPGCheck() bool {
114+
return r.remoteRepoGPGCheck
115+
}
116+
93117
// TargetDir retrieves the target directory configured for this [Runner].
94118
func (r *Runner) TargetDir() string {
95119
return r.targetDir
@@ -108,6 +132,36 @@ func (r *Runner) LocalRepoPaths() []string {
108132
return r.localRepoPaths
109133
}
110134

135+
// AddRemoteRepo adds a URI to a remote RPM repository (http:// or https://) to include during build.
136+
// Remote repositories are added with lower priority than local repositories.
137+
// Returns an error if the URI is invalid or uses an unsupported scheme.
138+
func (r *Runner) AddRemoteRepo(repoURI string) error {
139+
parsedURL, err := url.Parse(repoURI)
140+
if err != nil {
141+
return fmt.Errorf("invalid repository URI %#q:\n%w", repoURI, err)
142+
}
143+
144+
switch parsedURL.Scheme {
145+
case "http":
146+
slog.Warn("Using insecure HTTP for remote repository; consider using HTTPS instead",
147+
"uri", repoURI)
148+
case "https":
149+
// Valid, no warning needed
150+
default:
151+
return fmt.Errorf("unsupported scheme %#q for remote repository %#q: only http:// and https:// are supported",
152+
parsedURL.Scheme, repoURI)
153+
}
154+
155+
r.remoteRepoPaths = append(r.remoteRepoPaths, repoURI)
156+
157+
return nil
158+
}
159+
160+
// RemoteRepoPaths retrieves the remote repository URIs configured for this [Runner].
161+
func (r *Runner) RemoteRepoPaths() []string {
162+
return r.remoteRepoPaths
163+
}
164+
111165
// DescriptionDir retrieves the description directory configured for this [Runner].
112166
func (r *Runner) DescriptionDir() string {
113167
return r.descriptionDir
@@ -140,18 +194,42 @@ func (r *Runner) Build(ctx context.Context) error {
140194
"--target-dir", r.targetDir,
141195
}
142196

197+
// Add remote repositories using kiwi's --add-repo flag.
198+
// These have lower priority (50) than local repos so local repos can override.
199+
// Format: --add-repo=<uri>,rpm-md,<alias>,<priority>,,,,,,<repo_gpgcheck>
200+
// Note: repo_gpgcheck is at position 10 (1-indexed: source,type,alias,priority,imageinclude,
201+
// package_gpgcheck,{signing_keys},components,distribution,repo_gpgcheck).
202+
if len(r.remoteRepoPaths) > 0 && !r.remoteRepoGPGCheck {
203+
slog.Warn("Remote repositories are configured with GPG checking disabled",
204+
"count", len(r.remoteRepoPaths))
205+
}
206+
207+
for repoIndex, repoURI := range r.remoteRepoPaths {
208+
alias := fmt.Sprintf("remote-%d", repoIndex+1)
209+
// Priority 50 for all remote repos (lower priority than local repos at priority 1).
210+
// The trailing commas fill in: imageinclude, package_gpgcheck, signing_keys,
211+
// components, distribution, then repo_gpgcheck.
212+
gpgCheck := "true"
213+
if !r.remoteRepoGPGCheck {
214+
gpgCheck = "false"
215+
}
216+
217+
repoArg := fmt.Sprintf("%s,rpm-md,%s,50,,,,,,%s", repoURI, alias, gpgCheck)
218+
kiwiArgs = append(kiwiArgs, "--add-repo", repoArg)
219+
}
220+
143221
// Add local repositories using kiwi's --add-repo flag.
222+
// These have highest priority (1) to override both remote repos and kiwi defaults.
144223
// Format: --add-repo=dir://<abs-path>,rpm-md,<alias>,<priority>
145224
for repoIndex, repoPath := range r.localRepoPaths {
146225
absRepoPath, err := filepath.Abs(repoPath)
147226
if err != nil {
148227
return fmt.Errorf("failed to get absolute path for local repo %#q:\n%w", repoPath, err)
149228
}
150229

151-
// Use priority starting at 1 (highest priority) for local repos.
152-
priority := repoIndex + 1
153-
alias := fmt.Sprintf("local-%d", priority)
154-
repoArg := fmt.Sprintf("dir://%s,rpm-md,%s,%d", absRepoPath, alias, priority)
230+
// Priority 1 for all local repos (highest priority).
231+
alias := fmt.Sprintf("local-%d", repoIndex+1)
232+
repoArg := fmt.Sprintf("dir://%s,rpm-md,%s,1", absRepoPath, alias)
155233
kiwiArgs = append(kiwiArgs, "--add-repo", repoArg)
156234
}
157235

0 commit comments

Comments
 (0)