Skip to content

Commit 9f925c7

Browse files
authored
feat: allow deriving from differently-named upstream component (#389)
1 parent 9ce95ee commit 9f925c7

6 files changed

Lines changed: 166 additions & 5 deletions

File tree

internal/projectconfig/specsource.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,17 @@ package projectconfig
55

66
// Provides source information for locating the spec for a component.
77
type SpecSource struct {
8+
// SourceType indicates the type of source for the spec.
89
SourceType SpecSourceType `toml:"type" validate:"omitempty,oneof=local upstream" jsonschema:"required,enum=local,enum=,title=Source Type,description=The type of the spec source"`
910

10-
// Only relevant for local specs.
11+
// Path indicates the path to the spec file; only relevant for local specs.
1112
Path string `toml:"path,omitempty" json:",omitempty" validate:"excluded_unless=SourceType local,required_if=SourceType local" jsonschema:"title=Path,description=Path to the spec (if available locally),example=specs/mycomponent.spec"`
1213

13-
// Only relevant for upstream specs.
14+
// UpstreamDistro indicates the upstream distro providing the spec; only relevant for upstream specs.
1415
UpstreamDistro DistroReference `toml:"upstream-distro,omitempty" json:",omitempty" jsonschema:"title=Upstream distro,description=Reference to the upstream distro providing the spec"`
16+
17+
// UpstreamName indicates the name of the component in the upstream distro; only relevant for upstream specs.
18+
UpstreamName string `toml:"upstream-name,omitempty" json:",omitempty" validate:"excluded_unless=SourceType upstream" jsonschema:"title=Upstream component name,description=Name of the component in the upstream distro,example=different-name"`
1519
}
1620

1721
// Implements the [Stringer] interface.

internal/providers/sourceproviders/fedorasourceprovider.go

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"errors"
99
"fmt"
1010
"log/slog"
11+
"path/filepath"
1112
"strings"
1213

1314
"github.com/microsoft/azldev/internal/app/azldev/core/components"
@@ -75,13 +76,21 @@ func (g *FedoraSourcesProviderImpl) GetComponent(
7576
return errors.New("component name cannot be empty")
7677
}
7778

79+
upstreamNameToUse := componentName
80+
81+
// Figure out if there's an override for the upstream name. This can happen when the derived
82+
// component name differs from the name used in the upstream distro.
83+
if upstreamNameOverride := component.GetConfig().Spec.UpstreamName; upstreamNameOverride != "" {
84+
upstreamNameToUse = upstreamNameOverride
85+
}
86+
7887
if destDirPath == "" {
7988
return errors.New("destination path cannot be empty")
8089
}
8190

82-
gitRepoURL := strings.ReplaceAll(g.distroGitBaseURI, "$pkg", componentName)
91+
gitRepoURL := strings.ReplaceAll(g.distroGitBaseURI, "$pkg", upstreamNameToUse)
8392

84-
slog.Info("Getting component from git repo", "component", componentName)
93+
slog.Info("Getting component from git repo", "component", componentName, "upstreamComponent", upstreamNameToUse)
8594

8695
// Clone the repository directly to the destination
8796
err := g.gitProvider.Clone(
@@ -93,11 +102,30 @@ func (g *FedoraSourcesProviderImpl) GetComponent(
93102
return fmt.Errorf("failed to clone git repository %#q:\n%w", gitRepoURL, err)
94103
}
95104

105+
// Delete the .git directory so it's not packaged up.
106+
err = g.fs.RemoveAll(filepath.Join(destDirPath, ".git"))
107+
if err != nil {
108+
return fmt.Errorf("failed to remove .git directory from cloned repository at %#q:\n%w",
109+
destDirPath, err)
110+
}
111+
96112
// Extract sources from repo (downloads lookaside files into the repo)
97-
err = g.downloader.ExtractSourcesFromRepo(ctx, destDirPath, componentName)
113+
err = g.downloader.ExtractSourcesFromRepo(ctx, destDirPath, upstreamNameToUse)
98114
if err != nil {
99115
return fmt.Errorf("failed to extract sources from git repository:\n%w", err)
100116
}
101117

118+
// If the upstream name differs from the component name, we will need to rename the spec.
119+
if upstreamNameToUse != componentName {
120+
downloadedSpecPath := filepath.Join(destDirPath, upstreamNameToUse+".spec")
121+
desiredSpecPath := filepath.Join(destDirPath, componentName+".spec")
122+
123+
err := g.fs.Rename(downloadedSpecPath, desiredSpecPath)
124+
if err != nil {
125+
return fmt.Errorf("failed to rename fetched spec file from %#q to %#q:\n%w",
126+
downloadedSpecPath, desiredSpecPath, err)
127+
}
128+
}
129+
102130
return nil
103131
}

internal/providers/sourceproviders/fedorasourceprovider_test.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ func TestNewGitContentsProviderImpl(t *testing.T) {
122122
})
123123
}
124124

125+
//nolint:maintidx // It's long because of multiple test cases.
125126
func TestGetComponentFromGit(t *testing.T) {
126127
env := testutils.NewTestEnv(t)
127128

@@ -362,4 +363,108 @@ func TestGetComponentFromGit(t *testing.T) {
362363
require.Error(t, err)
363364
assert.ErrorIs(t, err, extractorError)
364365
})
366+
367+
t.Run("spec file renamed when upstream name differs from component name", func(t *testing.T) {
368+
ctrl := gomock.NewController(t)
369+
mockGitProvider := git_test.NewMockGitProvider(ctrl)
370+
mockExtractor := fedorasource_test.NewMockFedoraSourceDownloader(ctrl)
371+
372+
provider, err := sourceproviders.NewFedoraSourcesProviderImpl(
373+
env.FS(),
374+
mockGitProvider,
375+
mockExtractor,
376+
distGitBaseURI,
377+
branch,
378+
)
379+
require.NoError(t, err)
380+
381+
componentName := "my-component"
382+
upstreamName := "upstream-pkg"
383+
384+
mockComponent := components_testutils.NewMockComponent(ctrl)
385+
mockComponent.EXPECT().GetName().AnyTimes().Return(componentName)
386+
mockComponent.EXPECT().GetConfig().AnyTimes().Return(&projectconfig.ComponentConfig{
387+
Name: componentName,
388+
Spec: projectconfig.SpecSource{
389+
UpstreamName: upstreamName,
390+
},
391+
})
392+
393+
// Git clone creates spec file with upstream name
394+
upstreamRepoURL := "https://example.com/" + upstreamName + ".git"
395+
mockGitProvider.EXPECT().
396+
Clone(gomock.Any(), upstreamRepoURL, destDir, gomock.Any()).
397+
DoAndReturn(func(ctx context.Context, repoURL, cloneDir string, opts ...git.GitOptions) error {
398+
// Create spec file with upstream name
399+
specPath := cloneDir + "/" + upstreamName + ".spec"
400+
401+
return fileutils.WriteFile(
402+
env.FS(), specPath,
403+
[]byte("Name: "+upstreamName+"\nVersion: 1.0"),
404+
fileperms.PublicFile,
405+
)
406+
})
407+
408+
// Extractor succeeds
409+
mockExtractor.EXPECT().
410+
ExtractSourcesFromRepo(gomock.Any(), destDir, upstreamName).
411+
Return(nil)
412+
413+
err = provider.GetComponent(context.Background(), mockComponent, destDir)
414+
require.NoError(t, err)
415+
416+
// Verify spec file was renamed to component name
417+
renamedSpecPath := destDir + "/" + componentName + ".spec"
418+
exists, err := fileutils.Exists(env.FS(), renamedSpecPath)
419+
require.NoError(t, err)
420+
assert.True(t, exists, "spec file should be renamed to component name")
421+
422+
// Verify original upstream spec file no longer exists
423+
originalSpecPath := destDir + "/" + upstreamName + ".spec"
424+
exists, err = fileutils.Exists(env.FS(), originalSpecPath)
425+
require.NoError(t, err)
426+
assert.False(t, exists, "original upstream spec file should not exist after rename")
427+
})
428+
429+
t.Run("spec rename failure propagates when source file missing", func(t *testing.T) {
430+
ctrl := gomock.NewController(t)
431+
mockGitProvider := git_test.NewMockGitProvider(ctrl)
432+
mockExtractor := fedorasource_test.NewMockFedoraSourceDownloader(ctrl)
433+
434+
provider, err := sourceproviders.NewFedoraSourcesProviderImpl(
435+
env.FS(),
436+
mockGitProvider,
437+
mockExtractor,
438+
distGitBaseURI,
439+
branch,
440+
)
441+
require.NoError(t, err)
442+
443+
componentName := "my-component"
444+
upstreamName := "upstream-pkg"
445+
446+
mockComponent := components_testutils.NewMockComponent(ctrl)
447+
mockComponent.EXPECT().GetName().AnyTimes().Return(componentName)
448+
mockComponent.EXPECT().GetConfig().AnyTimes().Return(&projectconfig.ComponentConfig{
449+
Name: componentName,
450+
Spec: projectconfig.SpecSource{
451+
UpstreamName: upstreamName,
452+
},
453+
})
454+
455+
// Git clone succeeds but does NOT create the spec file (simulating missing file)
456+
upstreamRepoURL := "https://example.com/" + upstreamName + ".git"
457+
mockGitProvider.EXPECT().
458+
Clone(gomock.Any(), upstreamRepoURL, destDir, gomock.Any()).
459+
Return(nil)
460+
461+
// Extractor succeeds
462+
mockExtractor.EXPECT().
463+
ExtractSourcesFromRepo(gomock.Any(), destDir, upstreamName).
464+
Return(nil)
465+
466+
err = provider.GetComponent(context.Background(), mockComponent, destDir)
467+
require.Error(t, err)
468+
assert.Contains(t, err.Error(), "failed to rename fetched spec file")
469+
})
365470
}

scenario/__snapshots__/TestSnapshotsContainer_config_generate-schema_stdout_1.snap

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,14 @@
419419
"$ref": "#/$defs/DistroReference",
420420
"title": "Upstream distro",
421421
"description": "Reference to the upstream distro providing the spec"
422+
},
423+
"upstream-name": {
424+
"type": "string",
425+
"title": "Upstream component name",
426+
"description": "Name of the component in the upstream distro",
427+
"examples": [
428+
"different-name"
429+
]
422430
}
423431
},
424432
"additionalProperties": false,

scenario/__snapshots__/TestSnapshots_config_generate-schema_stdout_1.snap

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,14 @@
419419
"$ref": "#/$defs/DistroReference",
420420
"title": "Upstream distro",
421421
"description": "Reference to the upstream distro providing the spec"
422+
},
423+
"upstream-name": {
424+
"type": "string",
425+
"title": "Upstream component name",
426+
"description": "Name of the component in the upstream distro",
427+
"examples": [
428+
"different-name"
429+
]
422430
}
423431
},
424432
"additionalProperties": false,

schemas/azldev.schema.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,14 @@
419419
"$ref": "#/$defs/DistroReference",
420420
"title": "Upstream distro",
421421
"description": "Reference to the upstream distro providing the spec"
422+
},
423+
"upstream-name": {
424+
"type": "string",
425+
"title": "Upstream component name",
426+
"description": "Name of the component in the upstream distro",
427+
"examples": [
428+
"different-name"
429+
]
422430
}
423431
},
424432
"additionalProperties": false,

0 commit comments

Comments
 (0)