Skip to content

Commit aecff72

Browse files
authored
feat(mock): enable overriding mock config options (#485)
Enables an escape-hatch for threading through custom mock config options (e.g., config_opts style) via the azldev component build command-line.
1 parent 9d53684 commit aecff72

File tree

8 files changed

+113
-3
lines changed

8 files changed

+113
-3
lines changed

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ type ComponentBuildOptions struct {
3131

3232
LocalRepoPaths []string
3333
LocalRepoWithPublishPath string
34+
35+
// MockConfigOpts is an optional set of key-value config options that will be passed through
36+
// to mock as --config-opts key=value arguments.
37+
MockConfigOpts map[string]string
3438
}
3539

3640
// ComponentBuildResults summarizes the results of building a single component.
@@ -81,6 +85,8 @@ func NewBuildCmd() *cobra.Command {
8185
"Paths to local repositories to include during build (can be specified multiple times)")
8286
cmd.Flags().StringVar(&options.LocalRepoWithPublishPath, "local-repo-with-publish", "",
8387
"Path to local repository to include during build and publish built RPMs to")
88+
cmd.Flags().StringToStringVar(&options.MockConfigOpts, "mock-config-opt", nil,
89+
"Pass a configuration option through to mock (key=value, can be specified multiple times)")
8490

8591
// Mark flags as mutually exclusive.
8692
cmd.MarkFlagsMutuallyExclusive("srpm-only", "local-repo-with-publish")
@@ -163,7 +169,8 @@ func BuildComponent(
163169

164170
var buildEnv buildenv.RPMAwareBuildEnv
165171

166-
buildEnv, err = workdir.MkComponentBuildEnvironment(env, workDirFactory, component.GetConfig(), "build")
172+
buildEnv, err = workdir.MkComponentBuildEnvironment(env, workDirFactory, component.GetConfig(), "build",
173+
options.MockConfigOpts)
167174
if err != nil {
168175
return ComponentBuildResults{},
169176
fmt.Errorf("failed to create build environment for component %q:\n%w", component.GetName(), err)

internal/app/azldev/cmds/component/build_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,15 @@ func TestNewBuildCommand(t *testing.T) {
2020
}
2121
}
2222

23+
func TestNewBuildCommand_MockConfigOptFlag(t *testing.T) {
24+
cmd := componentcmds.NewBuildCmd()
25+
26+
// Verify that the --mock-config-opt flag is registered and functional.
27+
flag := cmd.Flags().Lookup("mock-config-opt")
28+
require.NotNil(t, flag)
29+
assert.Equal(t, "mock-config-opt", flag.Name)
30+
}
31+
2332
func TestRunBuildCommand_NoComponents(t *testing.T) {
2433
testEnv := testutils.NewTestEnv(t)
2534

internal/app/azldev/core/specs/spec.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ func (s *componentSpec) Parse() (specInfo *ComponentSpecDetails, err error) {
8383

8484
// Create a working build environment so we can run rpmspec in the target distro's context
8585
// (with all its macros, etc.).
86-
buildEnv, err := workdir.MkComponentBuildEnvironment(s.env, workDirFactory, &s.componentConfig, "spec-parse")
86+
buildEnv, err := workdir.MkComponentBuildEnvironment(s.env, workDirFactory, &s.componentConfig, "spec-parse", nil)
8787
if err != nil {
8888
return nil, fmt.Errorf("failed to create build environment for component %q:\n%w", s.componentConfig.Name, err)
8989
}

internal/app/azldev/core/workdir/workdir.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ func MkComponentBuildEnvironment(
8181
factory *Factory,
8282
component *projectconfig.ComponentConfig,
8383
label string,
84+
configOpts map[string]string,
8485
) (buildEnv buildenv.RPMAwareBuildEnv, err error) {
8586
// Create a work directory for us to place the environment under.
8687
workDir, err := factory.Create(component.Name, label)
@@ -103,6 +104,7 @@ func MkComponentBuildEnvironment(
103104
Dir: buildEnvDir,
104105
UserCreated: false,
105106
Description: fmt.Sprintf("Build: %s (%s)", component.Name, label),
107+
ConfigOpts: configOpts,
106108
},
107109
)
108110
if err != nil {

internal/buildenv/factory.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,7 @@ type CreateOptions struct {
3434
// Description optionally provides a human-readable description for the purpose of
3535
// this environment.
3636
Description string
37+
// ConfigOpts is an optional set of key-value pairs that will be passed through to the
38+
// underlying build environment backend as configuration overrides (e.g., mock's --config-opts).
39+
ConfigOpts map[string]string
3740
}

internal/buildenv/mockroot.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,14 @@ func (f *MockRootFactory) CreateRPMAwareEnv(options CreateOptions) (RPMAwareBuil
4343
// returns a [MockRoot] directly for callers that specifically want to interact with our concrete type.
4444
func (f *MockRootFactory) CreateMockRoot(options CreateOptions) (*MockRoot, error) {
4545
// Create and configure a mock runner using the mock package.
46+
runner := mock.NewRunner(f.ctx, f.mockConfigPath)
47+
48+
if len(options.ConfigOpts) > 0 {
49+
runner.WithConfigOpts(options.ConfigOpts)
50+
}
51+
4652
return &MockRoot{
47-
mockRunner: mock.NewRunner(f.ctx, f.mockConfigPath),
53+
mockRunner: runner,
4854
}, nil
4955
}
5056

internal/rpm/mock/mock.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"os/exec"
1313
"path/filepath"
1414
"regexp"
15+
"sort"
1516
"strings"
1617

1718
"github.com/brunoga/deep"
@@ -60,6 +61,10 @@ type Runner struct {
6061
// rootDir is the directory where the mock root will be created. If not set, it defaults to
6162
// being created under the baseDir.
6263
rootDir string // optional
64+
65+
// configOpts is an optional set of key-value pairs that will be passed through to mock as
66+
// --config-opts key=value arguments, allowing callers to override mock's configuration.
67+
configOpts map[string]string
6368
}
6469

6570
// BuildLogDetails encapsulates details extracted from mock build logs that may be relevant to
@@ -104,6 +109,7 @@ func (r *Runner) Clone() *Runner {
104109
noPreClean: r.noPreClean,
105110
baseDir: r.baseDir,
106111
rootDir: r.rootDir,
112+
configOpts: deep.MustCopy(r.configOpts),
107113
}
108114
}
109115

@@ -177,6 +183,19 @@ func (r *Runner) RootDir() string {
177183
return r.rootDir
178184
}
179185

186+
// WithConfigOpts updates the [Runner]'s configuration to set arbitrary config options that will
187+
// be passed through to mock as --config-opts key=value arguments.
188+
func (r *Runner) WithConfigOpts(opts map[string]string) *Runner {
189+
r.configOpts = opts
190+
191+
return r
192+
}
193+
194+
// ConfigOpts retrieves the set of arbitrary config options configured for this [Runner].
195+
func (r *Runner) ConfigOpts() map[string]string {
196+
return r.configOpts
197+
}
198+
180199
// Retrieves the path to the mock .cfg file used by this [Runner].
181200
func (r *Runner) ConfigPath() string {
182201
return r.mockConfigPath
@@ -624,6 +643,21 @@ func (r *Runner) getBaseArgs() (args []string) {
624643
args = append(args, "--rootdir", r.rootDir)
625644
}
626645

646+
// Emit any caller-specified config overrides.
647+
if len(r.configOpts) > 0 {
648+
// Sort keys for deterministic output.
649+
keys := make([]string, 0, len(r.configOpts))
650+
for k := range r.configOpts {
651+
keys = append(keys, k)
652+
}
653+
654+
sort.Strings(keys)
655+
656+
for _, k := range keys {
657+
args = append(args, "--config-opts", k+"="+r.configOpts[k])
658+
}
659+
}
660+
627661
if r.enableNetwork {
628662
args = append(args, "--enable-network")
629663
}

internal/rpm/mock/mock_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ func TestRunnerDefaults(t *testing.T) {
6464
assert.False(t, runner.HasNoPreClean())
6565
assert.Empty(t, runner.BaseDir())
6666
assert.Empty(t, runner.RootDir())
67+
assert.Empty(t, runner.ConfigOpts())
6768
}
6869

6970
func TestClone(t *testing.T) {
@@ -83,6 +84,7 @@ func TestClone(t *testing.T) {
8384
runner.WithRootDir(testRootDir)
8485
runner.WithNoPreClean()
8586
runner.EnableNetwork()
87+
runner.WithConfigOpts(map[string]string{"cleanup_on_success": "True", "cleanup_on_failure": "False"})
8688

8789
clone := runner.Clone()
8890

@@ -93,6 +95,7 @@ func TestClone(t *testing.T) {
9395
assert.True(t, clone.HasNoPreClean())
9496
assert.Equal(t, testBaseDir, clone.BaseDir())
9597
assert.Equal(t, testRootDir, clone.RootDir())
98+
assert.Equal(t, map[string]string{"cleanup_on_success": "True", "cleanup_on_failure": "False"}, clone.ConfigOpts())
9699
}
97100

98101
func TestGetRootPath(t *testing.T) {
@@ -280,3 +283,49 @@ func TestCmdInChroot_EnableNetworking(t *testing.T) {
280283
// Look for enable-network arg.
281284
assert.Contains(t, cmd.GetArgs(), "--enable-network")
282285
}
286+
287+
func TestCmdInChroot_ConfigOpts(t *testing.T) {
288+
ctx := newTestCtxWithMockPrereqsPresent()
289+
290+
runner := mock.NewRunner(ctx, testMockConfigPath)
291+
runner.WithConfigOpts(map[string]string{"cleanup_on_success": "True", "cleanup_on_failure": "False"})
292+
assert.Equal(t, map[string]string{"cleanup_on_success": "True", "cleanup_on_failure": "False"}, runner.ConfigOpts())
293+
294+
cmd, err := runner.CmdInChroot(ctx, []string{"arg"}, false /*interactive*/)
295+
require.NoError(t, err)
296+
require.NotNil(t, cmd)
297+
298+
// Look for config-opts args (keys should be sorted deterministically).
299+
cmdArgs := cmd.GetArgs()
300+
assert.Contains(t, cmdArgs, "--config-opts")
301+
assert.Contains(t, cmdArgs, "cleanup_on_failure=False")
302+
assert.Contains(t, cmdArgs, "cleanup_on_success=True")
303+
}
304+
305+
func TestBuildRPM_WithConfigOpts(t *testing.T) {
306+
ctx := newTestCtxWithMockPrereqsPresent()
307+
executedCmds := []string{}
308+
309+
ctx.CmdFactory.RunHandler = func(cmd *exec.Cmd) error {
310+
executedCmds = append(executedCmds, strings.Join(cmd.Args, " "))
311+
312+
require.NoError(t, fileutils.WriteFile(ctx.FS(), testRPMPath, []byte{}, fileperms.PrivateFile))
313+
314+
return nil
315+
}
316+
317+
runner := mock.NewRunner(ctx, testMockConfigPath)
318+
runner.WithConfigOpts(map[string]string{"cleanup_on_success": "True"})
319+
320+
mockOptions := mock.RPMBuildOptions{}
321+
322+
err := runner.BuildRPM(ctx, testSRPMPath, testOutputDirPath, mockOptions)
323+
require.NoError(t, err)
324+
325+
// Confirm that mock was invoked once.
326+
assert.Len(t, executedCmds, 1)
327+
328+
// Confirm the config-opts flag was passed through.
329+
mockCmd := executedCmds[0]
330+
assert.Contains(t, mockCmd, "--config-opts cleanup_on_success=True")
331+
}

0 commit comments

Comments
 (0)