From 29643c2e0656eade047c9e423ce01c7d096fcc09 Mon Sep 17 00:00:00 2001 From: Gianluca Zuccarelli Date: Mon, 17 Apr 2023 21:13:09 +0100 Subject: [PATCH] customizations: custom repo utility functions Create some utility functions that will be used for implementing custom repo configuration files. This commit adds these functions: - a helper to get the filename of a custom repo, or the `.repo` if the filename is empty - a function to convert the custom repos to a map of `RepoConfig`. This function also creates an `fsnode.File` for each inline gpg key set in the customizations and swaps the inline key for the file path. The function returns the map of `RepoConfig` and a list of `fsnode.File` containing the inline gpg keys. --- .../blueprint/repository_customizations.go | 69 +++++ .../repository_customizations_test.go | 255 ++++++++++++++++++ 2 files changed, 324 insertions(+) diff --git a/internal/blueprint/repository_customizations.go b/internal/blueprint/repository_customizations.go index 36666972d..ceeef3bbd 100644 --- a/internal/blueprint/repository_customizations.go +++ b/internal/blueprint/repository_customizations.go @@ -2,6 +2,10 @@ package blueprint import ( "fmt" + "net/url" + + "github.com/osbuild/osbuild-composer/internal/fsnode" + "github.com/osbuild/osbuild-composer/internal/rpmmd" ) type RepositoryCustomization struct { @@ -33,3 +37,68 @@ func validateCustomRepository(repo *RepositoryCustomization) error { } return nil } + +func (rc *RepositoryCustomization) getFilename() string { + if rc.Filename == "" { + return fmt.Sprintf("%s.repo", rc.Id) + } + return rc.Filename +} + +func RepoCustomizationsToRepoConfigAndGPGKeyFiles(repos []RepositoryCustomization) (map[string][]rpmmd.RepoConfig, []*fsnode.File, error) { + if len(repos) == 0 { + return nil, nil, nil + } + + repoMap := make(map[string][]rpmmd.RepoConfig, len(repos)) + var gpgKeyFiles []*fsnode.File + for _, repo := range repos { + filename := repo.getFilename() + convertedRepo := repo.customRepoToRepoConfig() + + // convert any inline gpgkeys to fsnode.File and + // replace the gpgkey with the file path + for idx, gpgkey := range repo.GPGKeys { + if _, ok := url.ParseRequestURI(gpgkey); ok != nil { + // create the file path + path := fmt.Sprintf("/etc/pki/rpm-gpg/RPM-GPG-KEY-%s-%d", repo.Id, idx) + // replace the gpgkey with the file path + convertedRepo.GPGKeys[idx] = fmt.Sprintf("file://%s", path) + // create the fsnode for the gpgkey keyFile + keyFile, err := fsnode.NewFile(path, nil, nil, nil, []byte(gpgkey)) + if err != nil { + return nil, nil, err + } + gpgKeyFiles = append(gpgKeyFiles, keyFile) + } + } + + repoMap[filename] = append(repoMap[filename], convertedRepo) + } + + return repoMap, gpgKeyFiles, nil +} + +func (repo RepositoryCustomization) customRepoToRepoConfig() rpmmd.RepoConfig { + urls := make([]string, len(repo.BaseURLs)) + copy(urls, repo.BaseURLs) + + keys := make([]string, len(repo.GPGKeys)) + copy(keys, repo.GPGKeys) + + repoConfig := rpmmd.RepoConfig{ + Id: repo.Id, + BaseURLs: urls, + GPGKeys: keys, + Name: repo.Name, + Metalink: repo.Metalink, + MirrorList: repo.Mirrorlist, + CheckGPG: repo.GPGCheck, + CheckRepoGPG: repo.RepoGPGCheck, + Priority: repo.Priority, + Enabled: repo.Enabled, + IgnoreSSL: !repo.SSLVerify, + } + + return repoConfig +} diff --git a/internal/blueprint/repository_customizations_test.go b/internal/blueprint/repository_customizations_test.go index fd5b57648..a827edf25 100644 --- a/internal/blueprint/repository_customizations_test.go +++ b/internal/blueprint/repository_customizations_test.go @@ -5,6 +5,8 @@ import ( "testing" "github.com/osbuild/osbuild-composer/internal/common" + "github.com/osbuild/osbuild-composer/internal/fsnode" + "github.com/osbuild/osbuild-composer/internal/rpmmd" "github.com/stretchr/testify/assert" ) @@ -78,3 +80,256 @@ func TestGetCustomRepositories(t *testing.T) { }) } } + +func TestCustomRepoFilename(t *testing.T) { + testCases := []struct { + Name string + Repo RepositoryCustomization + WantFilename string + }{ + { + Name: "Test default filename #1", + Repo: RepositoryCustomization{ + Id: "example-1", + BaseURLs: []string{"http://example-1.com"}, + }, + WantFilename: "example-1.repo", + }, + { + Name: "Test default filename #2", + Repo: RepositoryCustomization{ + Id: "example-2", + BaseURLs: []string{"http://example-1.com"}, + }, + WantFilename: "example-2.repo", + }, + { + Name: "Test custom filename", + Repo: RepositoryCustomization{ + Id: "example-1", + BaseURLs: []string{"http://example-1.com"}, + Filename: "test.repo", + }, + WantFilename: "test.repo", + }, + } + + for _, tt := range testCases { + t.Run(tt.Name, func(t *testing.T) { + got := tt.Repo.getFilename() + assert.Equal(t, tt.WantFilename, got) + }) + } +} + +func TestCustomRepoToRepoConfigAndGPGKeys(t *testing.T) { + ensureFileCreation := func(file *fsnode.File, err error) *fsnode.File { + t.Helper() + assert.NoError(t, err) + assert.NotNil(t, file) + return file + } + testCases := []struct { + Name string + Repos []RepositoryCustomization + WantRepoConfig map[string][]rpmmd.RepoConfig + WantGPGKeys []*fsnode.File + }{ + { + Name: "Test no gpg keys, no filenames", + Repos: []RepositoryCustomization{ + { + Id: "example-1", + BaseURLs: []string{"http://example-1.com"}, + SSLVerify: true, + }, + { + Id: "example-2", + BaseURLs: []string{"http://example-2.com"}, + SSLVerify: true, + }, + }, + WantRepoConfig: map[string][]rpmmd.RepoConfig{ + "example-1.repo": { + { + Id: "example-1", + BaseURLs: []string{"http://example-1.com"}, + GPGKeys: []string{}, + }, + }, + "example-2.repo": { + { + Id: "example-2", + BaseURLs: []string{"http://example-2.com"}, + GPGKeys: []string{}, + }, + }, + }, + WantGPGKeys: nil, + }, + { + Name: "Test no gpg keys, filenames", + Repos: []RepositoryCustomization{ + { + Id: "example-1", + BaseURLs: []string{"http://example-1.com"}, + SSLVerify: true, + Filename: "test-1.repo", + }, + { + Id: "example-2", + BaseURLs: []string{"http://example-2.com"}, + SSLVerify: true, + Filename: "test-2.repo", + }, + }, + WantRepoConfig: map[string][]rpmmd.RepoConfig{ + "test-1.repo": { + { + Id: "example-1", + BaseURLs: []string{"http://example-1.com"}, + GPGKeys: []string{}, + }, + }, + "test-2.repo": { + { + Id: "example-2", + BaseURLs: []string{"http://example-2.com"}, + GPGKeys: []string{}, + }, + }, + }, + WantGPGKeys: nil, + }, + { + Name: "Test remote gpgkeys", + Repos: []RepositoryCustomization{ + { + Id: "example-1", + BaseURLs: []string{"http://example-1.com"}, + GPGKeys: []string{"http://example-1.com/gpgkey"}, + GPGCheck: common.ToPtr(true), + SSLVerify: true, + }, + { + Id: "example-2", + BaseURLs: []string{"http://example-2.com"}, + GPGKeys: []string{"http://example-2.com/gpgkey"}, + GPGCheck: common.ToPtr(true), + SSLVerify: true, + }, + }, + WantRepoConfig: map[string][]rpmmd.RepoConfig{ + "example-1.repo": { + { + Id: "example-1", + BaseURLs: []string{"http://example-1.com"}, + GPGKeys: []string{"http://example-1.com/gpgkey"}, + CheckGPG: common.ToPtr(true), + }, + }, + "example-2.repo": { + { + Id: "example-2", + BaseURLs: []string{"http://example-2.com"}, + GPGKeys: []string{"http://example-2.com/gpgkey"}, + CheckGPG: common.ToPtr(true), + }, + }, + }, + WantGPGKeys: nil, + }, + { + Name: "Test inline gpgkeys", + Repos: []RepositoryCustomization{ + { + Id: "example-1", + BaseURLs: []string{"http://example-1.com"}, + GPGKeys: []string{"fake-gpg-key-1"}, + GPGCheck: common.ToPtr(true), + SSLVerify: true, + }, + { + Id: "example-2", + BaseURLs: []string{"http://example-2.com"}, + GPGKeys: []string{"fake-gpg-key-2"}, + GPGCheck: common.ToPtr(true), + SSLVerify: true, + }, + }, + WantRepoConfig: map[string][]rpmmd.RepoConfig{ + "example-1.repo": { + { + Id: "example-1", + BaseURLs: []string{"http://example-1.com"}, + GPGKeys: []string{"file:///etc/pki/rpm-gpg/RPM-GPG-KEY-example-1-0"}, + CheckGPG: common.ToPtr(true), + }, + }, + "example-2.repo": { + { + Id: "example-2", + BaseURLs: []string{"http://example-2.com"}, + GPGKeys: []string{"file:///etc/pki/rpm-gpg/RPM-GPG-KEY-example-2-0"}, + CheckGPG: common.ToPtr(true), + }, + }, + }, + WantGPGKeys: []*fsnode.File{ + ensureFileCreation(fsnode.NewFile("/etc/pki/rpm-gpg/RPM-GPG-KEY-example-1-0", nil, nil, nil, []byte("fake-gpg-key-1"))), + ensureFileCreation(fsnode.NewFile("/etc/pki/rpm-gpg/RPM-GPG-KEY-example-2-0", nil, nil, nil, []byte("fake-gpg-key-1"))), + }, + }, + { + Name: "Test multiple inline gpgkeys", + Repos: []RepositoryCustomization{ + { + Id: "example-1", + BaseURLs: []string{"http://example-1.com"}, + GPGKeys: []string{"fake-gpg-key-1", "fake-gpg-key-2"}, + GPGCheck: common.ToPtr(true), + SSLVerify: true, + }, + { + Id: "example-2", + BaseURLs: []string{"http://example-2.com"}, + GPGKeys: []string{"fake-gpg-key-1", "fake-gpg-key-2"}, + GPGCheck: common.ToPtr(true), + SSLVerify: true, + }, + }, + WantRepoConfig: map[string][]rpmmd.RepoConfig{ + "example-1.repo": { + { + Id: "example-1", + BaseURLs: []string{"http://example-1.com"}, + GPGKeys: []string{"file:///etc/pki/rpm-gpg/RPM-GPG-KEY-example-1-0", "file:///etc/pki/rpm-gpg/RPM-GPG-KEY-example-1-1"}, + CheckGPG: common.ToPtr(true), + }, + }, + "example-2.repo": { + { + Id: "example-2", + BaseURLs: []string{"http://example-2.com"}, + GPGKeys: []string{"file:///etc/pki/rpm-gpg/RPM-GPG-KEY-example-2-0", "file:///etc/pki/rpm-gpg/RPM-GPG-KEY-example-2-1"}, + CheckGPG: common.ToPtr(true), + }, + }, + }, + WantGPGKeys: []*fsnode.File{ + ensureFileCreation(fsnode.NewFile("/etc/pki/rpm-gpg/RPM-GPG-KEY-example-1-0", nil, nil, nil, []byte("fake-gpg-key-1"))), + ensureFileCreation(fsnode.NewFile("/etc/pki/rpm-gpg/RPM-GPG-KEY-example-1-1", nil, nil, nil, []byte("fake-gpg-key-2"))), + ensureFileCreation(fsnode.NewFile("/etc/pki/rpm-gpg/RPM-GPG-KEY-example-2-0", nil, nil, nil, []byte("fake-gpg-key-1"))), + ensureFileCreation(fsnode.NewFile("/etc/pki/rpm-gpg/RPM-GPG-KEY-example-2-1", nil, nil, nil, []byte("fake-gpg-key-2"))), + }, + }, + } + + for _, tt := range testCases { + t.Run(tt.Name, func(t *testing.T) { + got, _, err := RepoCustomizationsToRepoConfigAndGPGKeyFiles(tt.Repos) + assert.NoError(t, err) + assert.Equal(t, tt.WantRepoConfig, got) + }) + } +}