// Copyright (c) All respective contributors to the Peridot Project. All rights reserved.
// Copyright (c) 2021-2022 Rocky Enterprise Software Foundation, Inc. All rights reserved.
// Copyright (c) 2021-2022 Ctrl IQ, Inc. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors
// may be used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package workflow

import (
	"bufio"
	"context"
	"database/sql"
	"errors"
	"fmt"
	"io"
	"io/fs"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/google/uuid"
	"go.temporal.io/sdk/activity"
	"google.golang.org/protobuf/types/known/wrapperspb"
	"peridot.resf.org/apollo/rpmutils"
	"peridot.resf.org/peridot/db/models"
	peridotpb "peridot.resf.org/peridot/pb"
	"peridot.resf.org/peridot/rpmbuild"
	"peridot.resf.org/servicecatalog"
)

var (
	releaseDistRegex = regexp.MustCompile(".+\\.(el[^. \\t\\n]+)")
	// defaults are for el9
	DefaultBuildPkgGroup = []string{
		"bash",
		"bzip2",
		"coreutils",
		"cpio",
		"diffutils",
		"findutils",
		"gawk",
		"glibc-minimal-langpack",
		"grep",
		"gzip",
		"info",
		"make",
		"patch",
		"rpm-build",
		"sed",
		"shadow-utils",
		"tar",
		"unzip",
		"util-linux",
		"which",
		"xz",
	}
	// defaults are for EL9
	DefaultSrpmBuildPkgGroup = []string{
		"bash",
		"glibc-minimal-langpack",
		"gnupg2",
		"rpm-build",
		"shadow-utils",
	}
)

func runCmd(command string, args ...string) error {
	fmt.Printf("[+] %s %s\n", command, strings.Join(args, " "))

	var newArgs []string
	for _, arg := range args {
		newArgs = append(newArgs, strings.ReplaceAll(arg, "\"", ""))
	}

	cmd := exec.Command(command, newArgs...)

	stdoutPipe, err := cmd.StdoutPipe()
	if err != nil {
		return err
	}
	stderrPipe, err := cmd.StderrPipe()
	if err != nil {
		return err
	}

	err = cmd.Start()
	if err != nil {
		return err
	}

	var wg sync.WaitGroup
	wg.Add(2)

	go func(stdout io.ReadCloser) {
		defer wg.Done()
		scanner := bufio.NewScanner(stdout)
		for scanner.Scan() {
			fmt.Println(scanner.Text())
		}
	}(stdoutPipe)
	go func(stderr io.ReadCloser) {
		defer wg.Done()
		scanner := bufio.NewScanner(stderr)
		for scanner.Scan() {
			fmt.Println(scanner.Text())
		}
	}(stderrPipe)

	wg.Wait()

	return cmd.Wait()
}

func findRpms() ([]string, error) {
	var rpms []string
	err := filepath.Walk(rpmbuild.GetCloneDirectory()+"/RPMS", func(path string, info fs.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if info.IsDir() {
			return nil
		}

		if strings.HasSuffix(filepath.Base(path), ".rpm") && !strings.HasSuffix(filepath.Base(path), ".src.rpm") {
			rpms = append(rpms, path)
			return nil
		}

		return nil
	})
	if err != nil {
		if strings.Contains(err.Error(), "no such file or directory") {
			return rpms, nil
		}
		return nil, err
	}

	return rpms, nil
}

func addExtraFiles(extraOptions *peridotpb.ExtraBuildOptions) error {
	if extraOptions.BuildArchExtraFiles != nil {
		for k, v := range extraOptions.BuildArchExtraFiles {
			// Create dir if now exists
			dir := filepath.Dir(k)
			_, err := os.Stat(dir)
			if err != nil {
				if os.IsNotExist(err) {
					err = os.MkdirAll(dir, 0755)
					if err != nil {
						return err
					}
				} else {
					return err
				}
			}
			err = ioutil.WriteFile(k, []byte(v), 0644)
			if err != nil {
				return err
			}
		}
	}

	return nil
}

func getRepoUrl(arch string, nRepoUrl string) string {
	repoUrl := strings.NewReplacer("$arch", arch, "$basearch", arch).Replace(nRepoUrl)
	// Sometimes i686 may be called i386 for repo purposes
	// Koji uses i386 for example for repo path but builds for i686
	// Let's just make sure we can use the repo
	if arch == "i686" {
		res, err := http.Get(fmt.Sprintf("%s/repodata/repomd.xml", repoUrl))
		if err != nil || res.StatusCode != 200 {
			repoUrl = strings.NewReplacer("$arch", "i386", "$basearch", "i386").Replace(nRepoUrl)
		}
	} else if arch == "noarch" {
		repoUrl = nRepoUrl
	}

	return repoUrl
}

func (c *Controller) repos(projectId string, arch string, extraOptions *peridotpb.ExtraBuildOptions) (map[string]string, error) {
	ret := map[string]string{}
	extraRepos := extraOptions.ExtraYumrepofsRepos

	yumrepofsRepos := extraRepos
	hasAll := false
	for _, repo := range yumrepofsRepos {
		if repo.Name == "all" {
			hasAll = true
		}
	}
	if !hasAll {
		yumrepofsRepos = []*peridotpb.ExtraYumrepofsRepo{
			{
				Name: "all",
			},
		}
		yumrepofsRepos = append(yumrepofsRepos, extraRepos...)
	}

	for i, repo := range yumrepofsRepos {
		repoUrl := getRepoUrl(arch, servicecatalog.YumrepofsRepo(projectId, repo.Name, "$arch"))

		yumrepofsConfig := `[yumrepofs_{i}]
name=Peridot Internal - Yumrepofs {i}
baseurl={url}
gpgcheck=0
enabled=1
priority={i}
module_hotfixes={mhf}
skip_if_unavailable=1`

		if extraOptions.ExcludePackages != nil && len(extraOptions.ExcludePackages) > 0 && !repo.IgnoreExclude {
			yumrepofsConfig += fmt.Sprintf("\nexclude=%s", strings.Join(extraOptions.ExcludePackages, " "))
		}

		mhf := "0"
		if repo.ModuleHotfixes {
			mhf = "1"
		}
		iStr := strconv.Itoa(i)
		rendered := strings.NewReplacer("{url}", repoUrl, "{i}", iStr, "{mhf}", mhf).Replace(yumrepofsConfig)
		ret[fmt.Sprintf("/etc/yum.repos.d/yumrepofs_%d.repo", i)] = rendered
	}

	repos, err := c.db.GetExternalRepositoriesForProject(projectId)
	if err != nil {
		return nil, err
	}

	for i, repo := range repos {
		repoConfig := `[peridotexternal_{i}]
name=Peridot External {i}
baseurl={url}
gpgcheck=0
enabled=1
priority={priority}
module_hotfixes={module_hotfixes}`

		if extraOptions.ExcludePackages != nil && len(extraOptions.ExcludePackages) > 0 {
			repoConfig += fmt.Sprintf("\nexclude=%s", strings.Join(extraOptions.ExcludePackages, " "))
		}

		repoUrl := strings.NewReplacer("$arch", arch, "$basearch", arch).Replace(repo.Url)
		// Sometimes i686 may be called i386 for repo purposes
		// Koji uses i386 for example for repo path but builds for i686
		// Let's just make sure we can use the repo
		if arch == "i686" {
			res, err := http.Get(fmt.Sprintf("%s/repodata/repomd.xml", repoUrl))
			if err != nil || res.StatusCode != 200 {
				repoUrl = strings.NewReplacer("$arch", "i386", "$basearch", "i386").Replace(repo.Url)
			}
		} else if arch == "noarch" {
			repoUrl = repo.Url
		}

		priority := strconv.Itoa(repo.Priority)
		moduleHotfixes := "0"
		if repo.ModuleHotfixes {
			moduleHotfixes = "1"
		}
		rendered := strings.NewReplacer("{i}", strconv.Itoa(i), "{url}", repoUrl, "{priority}", priority, "{module_hotfixes}", moduleHotfixes).Replace(repoConfig)
		ret[fmt.Sprintf("/etc/yum.repos.d/peridotexternal_%d.repo", i)] = rendered
	}

	for _, plugin := range c.plugins {
		entries := plugin.RepoEntries()
		if entries == nil {
			continue
		}

		for _, entry := range entries {
			ret[fmt.Sprintf("/etc/yum.repos.d/%s.repo", uuid.New().String())] = entry
		}
	}

	return ret, nil
}

func (c *Controller) initializeRepos(projectId string, arch string, extraOptions *peridotpb.ExtraBuildOptions) error {
	repos, err := c.repos(projectId, arch, extraOptions)
	if err != nil {
		return err
	}
	for k, v := range repos {
		err := os.WriteFile(k, []byte(v), 0644)
		if err != nil {
			return err
		}
	}

	return nil
}

func (c *Controller) buildMacros(project *models.Project, packageVersion *models.PackageVersion) map[string]string {
	distTag := fmt.Sprintf("el%d", project.MajorVersion)
	if project.DistTagOverride.Valid {
		distTag = project.DistTagOverride.String
	}
	if project.FollowImportDist && packageVersion != nil && !strings.Contains(packageVersion.Release, ".module+") {
		if releaseDistRegex.MatchString(packageVersion.Release) {
			subMatch := releaseDistRegex.FindStringSubmatch(packageVersion.Release)
			distTag = subMatch[1]
		}
	}
	majorVersion := strconv.Itoa(project.MajorVersion)

	vendor := strings.ToUpper(string(project.AdditionalVendor[0])) + project.AdditionalVendor[1:]
	packager := vendor
	if project.VendorMacro.String != "" {
		vendor = project.VendorMacro.String
	}
	if project.PackagerMacro.String != "" {
		packager = project.PackagerMacro.String
	}

	ret := map[string]string{
		"%__bootstrap":  "~bootstrap",
		"%vendor":       vendor,
		"%packager":     packager,
		"%distribution": project.Name,
		"%dist":         "%{!?distprefix0:%{?distprefix}}%{expand:%{lua:for i=0,9999 do print(\"%{?distprefix\" .. i ..\"}\") end}}." + distTag + "%{?with_bootstrap:~bootstrap}",
	}

	switch project.TargetVendor {
	case "redhat":
		ret["%rhel"] = majorVersion
	case "suse":
		ret["%sles_version"] = "0"
		ret["%suse_version"] = fmt.Sprintf("%s00", majorVersion)
	}

	return ret
}

func (c *Controller) setBuildMacros(project *models.Project, packageVersion *models.PackageVersion) error {
	err := os.Remove("/etc/rpm/macros.dist")
	if err != nil && !os.IsNotExist(err) {
		return err
	}

	macros := c.buildMacros(project, packageVersion)
	var rendered string
	for k, v := range macros {
		rendered += fmt.Sprintf("%s %s\n", k, v)
	}

	err = os.WriteFile("/etc/rpm/macros.dist", []byte(rendered), 0644)
	if err != nil {
		return err
	}

	return nil
}

func (c *Controller) yumConfig(project *models.Project) string {
	yumConfig := `[main]
debuglevel=1
retries=20
obsoletes=1
gpgcheck=0
assumeyes=1
keepcache=1
syslog_ident=peridotbuilder
syslog_device=
metadata_expire=0
install_weak_deps=0
protected_packages=
user_agent=peridotbuilder`

	switch project.TargetVendor {
	case "redhat":
		yumConfig += "\nmodule_platform_id=platform:el{majorVersion}"
	}

	majorVersion := strconv.Itoa(project.MajorVersion)
	return strings.NewReplacer("{majorVersion}", majorVersion).Replace(yumConfig)
}

func (c *Controller) setYumConfig(project *models.Project) error {
	yumConfigPath := "/etc/yum.conf"

	_, err := os.Stat(yumConfigPath)
	if err != nil {
		if !os.IsNotExist(err) {
			return err
		}
		yumConfigPath = "/etc/dnf/dnf.conf"
	}
	yumConfigPath, err = filepath.EvalSymlinks(yumConfigPath)
	if err != nil {
		return err
	}

	err = os.WriteFile(yumConfigPath, []byte(c.yumConfig(project)), 0644)
	if err != nil {
		return err
	}

	return nil
}

func (c *Controller) chrootPkgs(project *models.Project, pkgGroup []string) []string {
	chrootPkgs := pkgGroup
	if project.TargetVendor == "redhat" {
		chrootPkgs = append(chrootPkgs, "redhat-rpm-config")
	}
	for _, plugin := range c.plugins {
		if plugin.Packages() != nil {
			chrootPkgs = append(chrootPkgs, plugin.Packages()...)
		}
	}

	return chrootPkgs
}

func (c *Controller) mockConfig(project *models.Project, packageVersion *models.PackageVersion, extra *peridotpb.ExtraBuildOptions, arch string, hostArch string, pkgGroup []string) (string, error) {
	// If we're building for i686 then force host arch to i686 even if we're building on x86_64
	if arch == "i686" {
		hostArch = "i686"
	}

	buildMacros := c.buildMacros(project, packageVersion)
	mockConfig := `
config_opts['root'] = '{additionalVendor}-{majorVersion}-{hostArch}'
config_opts['target_arch'] = '{arch}'
config_opts['legal_host_arches'] = [{hostArches}]
config_opts['chroot_setup_cmd'] = 'install {chrootPkgs}'
config_opts['dist'] = '{dist}'
config_opts['releasever'] = '{majorVersion}'
config_opts['package_manager'] = 'dnf'
config_opts['extra_chroot_dirs'] = [ '/run/lock' ]
config_opts['rpmbuild_command'] = '{rpmbuildCommand}'

config_opts['plugin_conf']['ccache_enable'] = False
config_opts['plugin_conf']['root_cache_enable'] = False
config_opts['plugin_conf']['yum_cache_enable'] = False
config_opts['rpmbuild_networking'] = {rpmbuildNetworking}
config_opts['use_host_resolv'] = {rpmbuildNetworking}
config_opts['print_main_output'] = True

config_opts['macros']['%_rpmfilename'] = '%%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm'
config_opts['macros']['%_host'] = '{hostArch}-{targetVendor}-linux-gnu'
config_opts['macros']['%_host_cpu'] = '{hostArch}'
config_opts['macros']['%_vendor'] = "{targetVendor}"
config_opts['macros']['%_vendor_host'] = "{targetVendor}"

config_opts['module_setup_commands'] = [{moduleSetupCommands}]

`
	for k, v := range buildMacros {
		mockConfig += fmt.Sprintf("config_opts['macros']['%s'] = '%s'\n", k, v)
	}

	if extra == nil {
		extra = &peridotpb.ExtraBuildOptions{}
	}
	if extra.BuildArchExtraFiles == nil {
		extra.BuildArchExtraFiles = map[string]string{}
	}
	var macrosRendered string
	for k, v := range buildMacros {
		macrosRendered += fmt.Sprintf("%s %s\n", k, v)
	}
	extra.BuildArchExtraFiles["/usr/lib/rpm/macros.d/macros.dist"] = macrosRendered

	for k, v := range extra.BuildArchExtraFiles {
		mockConfig += fmt.Sprintf(`config_opts['files']['%s'] = """
%s
"""
`, strings.TrimPrefix(k, "/"), v)
	}

	for _, env := range rpmbuild.CmdDefaultArgs {
		spl := strings.SplitN(env, "=", 2)
		mockConfig += fmt.Sprintf("config_opts['environment']['%s'] = '%s'\n", spl[0], spl[1])
	}

	var tmpHostArches []string
	if hostArch == "i686" {
		tmpHostArches = []string{"i386", "i486", "i586", "i686", "x86_64"}
	} else {
		tmpHostArches = []string{hostArch}
	}
	tmpHostArches = append(tmpHostArches, "noarch")
	var hostArches []string
	for _, v := range tmpHostArches {
		hostArches = append(hostArches, fmt.Sprintf("'%s'", v))
	}

	var moduleSetupCommands []string
	for _, module := range extra.Modules {
		moduleSetupCommands = append(moduleSetupCommands, fmt.Sprintf("('enable', '%s')", module))
	}

	mockConfig += "\n"
	mockConfig += `
config_opts['dnf.conf'] = """
{yumConfig}
reposdir=/dev/null
cachedir=/var/cache/yum
logfile=/var/log/yum.log
mdpolicy=group:primary
metadata_expire=0

`

	repos, err := c.repos(project.ID.String(), arch, extra)
	if err != nil {
		return "", err
	}
	for _, repo := range repos {
		mockConfig += fmt.Sprintf("%s\n", repo)
	}
	mockConfig += `"""
`

	yumConfig := c.yumConfig(project)

	rpmbuildNetworking := "False"
	if extra.EnableNetworking {
		rpmbuildNetworking = "True"
	}
	rendered := strings.NewReplacer(
		"{additionalVendor}", project.AdditionalVendor,
		"{majorVersion}", strconv.Itoa(project.MajorVersion),
		"{arch}", arch,
		"{hostArch}", hostArch,
		"{hostArches}", strings.Join(hostArches, ","),
		"{dist}", buildMacros["%dist"],
		"{yumConfig}", yumConfig,
		"{chrootPkgs}", strings.Join(c.chrootPkgs(project, pkgGroup), " "),
		"{rpmbuildCommand}", rpmbuild.ExecPath,
		"{targetVendor}", project.TargetVendor,
		"{moduleSetupCommands}", strings.Join(moduleSetupCommands, ","),
		"{rpmbuildNetworking}", rpmbuildNetworking,
	).Replace(mockConfig)

	return rendered, nil
}

func (c *Controller) writeMockConfig(project *models.Project, packageVersion *models.PackageVersion, extra *peridotpb.ExtraBuildOptions, arch string, hostArch string, pkgGroup []string) error {
	mockConfig, err := c.mockConfig(project, packageVersion, extra, arch, hostArch, pkgGroup)
	if err != nil {
		return err
	}
	return ioutil.WriteFile("/var/peridot/mock.cfg", []byte(mockConfig), 0644)
}

// BuildArchActivity builds a package for a given arch
// 26.04.2022: This activity had a huge rework with shelling out and chroot
// Previously it only used Go calls, but architectures like i686
// forced us to change to this method
// 03.05.2022: This activity was once again reworked to use mock.
// The reason being odd issues caused by the way we did chroot/unshare.
// This only affected a few packages, but we're in a hurry.
// Current implementation is broken for modules
// todo(mustafa): Evaluate if we can skip chroot again
func (c *Controller) BuildArchActivity(ctx context.Context, projectId string, packageName string, disableChecks bool, packageVersion *models.PackageVersion, uploadSRPMResult *UploadActivityResult, task *models.Task, arch string, extraOptions *peridotpb.ExtraBuildOptions) error {
	go func() {
		for {
			activity.RecordHeartbeat(ctx)
			time.Sleep(10 * time.Second)
		}
	}()

	err := c.db.SetTaskStatus(task.ID.String(), peridotpb.TaskStatus_TASK_STATUS_RUNNING)
	if err != nil {
		return err
	}

	defer func() {
		err := c.db.SetTaskStatus(task.ID.String(), task.Status)
		if err != nil {
			c.log.Errorf("could not set task status in BuildSRPMActivity: %v", err)
		}
	}()

	// should fall back to FAILED in case it actually fails before we
	// can set it to SUCCEEDED
	task.Status = peridotpb.TaskStatus_TASK_STATUS_FAILED

	projects, err := c.db.ListProjects(&peridotpb.ProjectFilters{Id: wrapperspb.String(projectId)})
	if err != nil {
		return err
	}
	project := projects[0]

	pkgEo, err := c.db.GetExtraOptionsForPackage(project.ID.String(), packageName)
	if err != nil && err != sql.ErrNoRows {
		return err
	}

	objBytes, err := c.storage.ReadObject(uploadSRPMResult.ObjectName)
	if err != nil {
		return err
	}

	cloneDir := rpmbuild.GetCloneDirectory()
	err = os.MkdirAll(filepath.Join(cloneDir, "SRPMS"), 0755)
	if err != nil {
		return err
	}
	err = os.MkdirAll(filepath.Join(cloneDir, "RPMS"), 0755)
	if err != nil {
		return err
	}

	srpmPath := filepath.Join(cloneDir, "SRPMS", filepath.Base(uploadSRPMResult.ObjectName))
	f, err := os.OpenFile(srpmPath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0644)
	if err != nil {
		return err
	}

	_, err = f.Write(objBytes)
	if err != nil {
		return err
	}

	err = runCmd("chown", "-R", "peridotbuilder:mock", cloneDir)
	if err != nil {
		return fmt.Errorf("could not chown clone dir: %v", err)
	}

	// todo(mustafa): Temporal doesn't support Activity interceptors yet.
	// todo(mustafa): https://github.com/temporalio/proposals/pull/45
	if err := c.preExecPlugins("BuildArchActivity"); err != nil {
		return err
	}

	var pkgGroup = DefaultBuildPkgGroup

	if len(project.BuildStagePackages) != 0 {
		pkgGroup = project.BuildStagePackages
	}

	if pkgEo != nil {
		if len(pkgEo.DependsOn) != 0 {
			for _, pkg := range pkgEo.DependsOn {
				pkgGroup = append(pkgGroup, pkg)
			}
		}
	}

	hostArch := os.Getenv("REAL_BUILD_ARCH")
	err = c.writeMockConfig(&project, packageVersion, extraOptions, arch, hostArch, pkgGroup)
	if err != nil {
		return fmt.Errorf("could not write mock config: %v", err)
	}
	args := []string{
		"mock",
		"--isolation=simple",
		"-r",
		"/var/peridot/mock.cfg",
		"--target",
		arch,
		"--resultdir",
		filepath.Join(cloneDir, "RPMS"),
	}
	if disableChecks {
		args = append(args, "--nocheck")
	}
	if pkgEo != nil {
		for _, with := range pkgEo.WithFlags {
			args = append(args, "--with="+with)
		}
		for _, without := range pkgEo.WithoutFlags {
			args = append(args, "--without="+without)
		}
	}
	args = append(args, srpmPath)

	cmd := exec.Command("/bundle/fork-exec.py", args...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	err = cmd.Run()
	if err != nil {
		return fmt.Errorf("could not mock build: %v", err)
	}

	// todo(mustafa): Remove once Temporal supports Activity interceptors
	if err := c.postExecPlugins("BuildArchActivity"); err != nil {
		return err
	}

	task.Status = peridotpb.TaskStatus_TASK_STATUS_SUCCEEDED

	return nil
}

func (c *Controller) UploadArchActivity(ctx context.Context, projectId string, parentTaskId string) ([]*UploadActivityResult, error) {
	go func() {
		for {
			activity.RecordHeartbeat(ctx)
			time.Sleep(4 * time.Second)
		}
	}()

	rpms, err := findRpms()
	if err != nil {
		return nil, err
	}

	var ret []*UploadActivityResult

	for _, rpm := range rpms {
		var nvr []string
		base := strings.TrimSuffix(filepath.Base(rpm), ".rpm")
		if rpmutils.NVRUnusualRelease().MatchString(base) {
			nvr = rpmutils.NVRUnusualRelease().FindStringSubmatch(base)
		} else if rpmutils.NVR().MatchString(base) {
			nvr = rpmutils.NVR().FindStringSubmatch(base)
		}
		if !rpmutils.NVR().MatchString(base) {
			return nil, errors.New("invalid rpm")
		}
		res, err := c.uploadArtifact(projectId, parentTaskId, rpm, nvr[4], peridotpb.TaskType_TASK_TYPE_BUILD_ARCH_UPLOAD)
		if err != nil {
			return nil, err
		}
		ret = append(ret, res)
	}

	return ret, nil
}
