Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: all repo use same logic #966

Merged
merged 1 commit into from
Aug 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions internal/pkg/plugininstaller/common/common_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package common_test

import (
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

func TestCommon(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "PluginInstaller Common Suite")
}
192 changes: 102 additions & 90 deletions internal/pkg/plugininstaller/common/repo.go
Original file line number Diff line number Diff line change
@@ -1,106 +1,40 @@
package common

import (
"io/fs"
"os"
"fmt"
"path/filepath"
"regexp"
"strings"

"github.com/devstream-io/devstream/pkg/util/file"
"github.com/devstream-io/devstream/pkg/util/github"
"github.com/devstream-io/devstream/pkg/util/gitlab"
"github.com/devstream-io/devstream/pkg/util/log"
"github.com/devstream-io/devstream/pkg/util/template"
)

const (
transitBranch = "init-with-devstream"
defaultCommitMsg = "init with devstream"
)

// Repo is the repo info of github or gitlab
type Repo struct {
Owner string `validate:"required_without=Org"`
Org string `validate:"required_without=Owner"`
Repo string `validate:"required"`
Branch string `validate:"required"`
Owner string `validate:"required_without=Org" mapstructure:"owner"`
Org string `validate:"required_without=Owner" mapstructure:"org"`
Repo string `validate:"required" mapstructure:"repo"`
Branch string `mapstructure:"branch"`
PathWithNamespace string
RepoType string `validate:"oneof=gitlab github" mapstructure:"repo_type"`
// This is config for gitlab
BaseURL string `validate:"omitempty,url"`
Visibility string `validate:"omitempty,oneof=public private internal"`
}

// CreateLocalRepoPath create local path for repo
func (d *Repo) CreateLocalRepoPath(workpath string) (string, error) {
localPath := filepath.Join(workpath, d.Repo)
if err := os.MkdirAll(localPath, os.ModePerm); err != nil {
return "", err
}
return localPath, nil
BaseURL string `validate:"omitempty,url" mapstructure:"base_url"`
Visibility string `validate:"omitempty,oneof=public private internal" mapstructure:"visibility"`
}

// Generate is a walker func to render and copy files from srcRepoPath to dstRepoPath
func (d *Repo) GenerateRenderWalker(
srcRepoPath, dstRepoPath, appNamePlaceHolder string, renderConfig map[string]interface{},
) func(path string, info fs.FileInfo, err error) error {
return func(path string, info fs.FileInfo, err error) error {
if err != nil {
log.Debugf("Walk error: %s.", err)
return err
}

relativePath := strings.Replace(path, srcRepoPath, "", 1)
if strings.Contains(path, ".git/") {
log.Debugf("Walk: ignore file %s.", "./git/xxx")
return nil
}

if strings.HasSuffix(path, "README.md") {
log.Debugf("Walk: ignore file %s.", "README.md")
return nil
}

// replace template with appName
outputWithRepoName, err := replaceAppNameInPathStr(relativePath, appNamePlaceHolder, d.Repo)
if err != nil {
log.Debugf("Walk: Replace file name failed %s.", path)
return err
}
outputPath := filepath.Join(dstRepoPath, outputWithRepoName)

if info.IsDir() {
log.Debugf("Walk: found dir: %s.", path)
if err != nil {
return err
}

if err := os.MkdirAll(outputPath, os.ModePerm); err != nil {
return err
}
log.Debugf("Walk: new output dir created: %s.", outputPath)
return nil
}

log.Debugf("Walk: found file: %s.", path)

// if file endswith tpl, render this file, else copy this file directly
if strings.Contains(path, "tpl") {
outputPath = strings.TrimSuffix(outputPath, ".tpl")
return template.RenderForFile("repo-scaffolding", path, outputPath, renderConfig)
}
return file.CopyFile(path, outputPath)
}
}

// CreateRepoRenderConfig will generate template render variables
func (d *Repo) CreateRepoRenderConfig() map[string]interface{} {
var owner = d.Owner
if d.Org != "" {
owner = d.Org
}

// BuildRepoRenderConfig will generate template render variables
func (d *Repo) BuildRepoRenderConfig() map[string]interface{} {
renderConfigMap := map[string]interface{}{
"AppName": d.Repo,
"Repo": map[string]string{
"Name": d.Repo,
"Owner": owner,
"Owner": d.getRepoOwner(),
},
}
return renderConfigMap
Expand All @@ -126,21 +60,99 @@ func (d *Repo) CreateGitlabClient() (*gitlab.Client, error) {
return gitlab.NewClient(gitlab.WithBaseURL(d.BaseURL))
}

// BuildgitlabOpts build gitlab connection options
func (d *Repo) BuildgitlabOpts() *gitlab.CreateProjectOptions {
// buildgitlabOpts build gitlab connection options
func (d *Repo) buildgitlabOpts() *gitlab.CreateProjectOptions {
return &gitlab.CreateProjectOptions{
Name: d.Repo,
Branch: d.Branch,
Branch: d.getBranch(),
Namespace: d.Org,
Visibility: d.Visibility,
}
}

func replaceAppNameInPathStr(filePath, appNamePlaceHolder, appName string) (string, error) {
if !strings.Contains(filePath, appNamePlaceHolder) {
return filePath, nil
// CreateAndRenderLocalRepo will download repo from source repo and render it locally
func (d *Repo) CreateAndRenderLocalRepo(appName string, vars map[string]interface{}) (string, error) {
//TODO(steinliber) support gtlab later
if d.RepoType != "github" {
return "", fmt.Errorf("the download target repo is currently only supported github")
}
// 1. download zip file and unzip this file then render folders
downloadURL := d.GetGithubDownloadURL()
projectDir, err := file.NewTemplate().FromRemote(downloadURL).UnzipFile().RenderRepoDIr(
appName, vars,
).Run()
if err != nil {
log.Debugf("reposcaffolding process file error: %s", err)
return "", err
}
// 2. join download path and repo name to get repo path
repoDirName := d.getRepoNameWithBranch()
return filepath.Join(projectDir, repoDirName), nil
}

// This Func will push repo to remote base on repoType
func (d *Repo) Push(repoPath string) error {
switch d.RepoType {
case "github":
ghClient, err := d.CreateGithubClient(true)
if err != nil {
return err
}
return ghClient.PushInitRepo(transitBranch, d.getBranch(), repoPath)
case "gitlab":
c, err := d.CreateGitlabClient()
if err != nil {
return err
}
return c.PushInitRepo(d.buildgitlabOpts(), d.PathWithNamespace, repoPath, defaultCommitMsg)
default:
return fmt.Errorf("scaffolding not support repo destination: %s", d.RepoType)
}
}

func (d *Repo) Delete() error {
switch d.RepoType {
case "github":
// 1. create ghClient
ghClient, err := d.CreateGithubClient(true)
if err != nil {
return err
}
// 2. delete github repo
return ghClient.DeleteRepo()
case "gitlab":
gLclient, err := d.CreateGitlabClient()
if err != nil {
return err
}
return gLclient.DeleteProject(d.PathWithNamespace)
default:
return fmt.Errorf("scaffolding not support repo destination: %s", d.RepoType)
}
}

func (d *Repo) getBranch() string {
if d.Branch != "" {
return d.Branch
}
return "main"
}

func (d *Repo) getRepoNameWithBranch() string {
return fmt.Sprintf("%s-%s", d.Repo, d.getBranch())
}

func (d *Repo) GetGithubDownloadURL() string {
latestCodeZipfileDownloadURL := fmt.Sprintf(
github.DefaultLatestCodeZipfileDownloadUrlFormat, d.getRepoOwner(), d.Repo, d.getBranch(),
)
log.Debugf("LatestCodeZipfileDownloadUrl: %s.", latestCodeZipfileDownloadURL)
return latestCodeZipfileDownloadURL
}

func (d *Repo) getRepoOwner() string {
if d.Org != "" {
return d.Org
}
newFilePath := regexp.MustCompile(appNamePlaceHolder).ReplaceAllString(filePath, appName)
log.Debugf("Replace file path place holder. Before: %s, after: %s.", filePath, newFilePath)
return newFilePath, nil
return d.Owner
}
105 changes: 105 additions & 0 deletions internal/pkg/plugininstaller/common/repo_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package common

import (
"fmt"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

var _ = Describe("Repo Struct", func() {
var (
repo *Repo
owner, org, repoName, branch, pathWithNamespace, repoType, baseUrl, visibility string
)
BeforeEach(func() {
owner = "test_owner"
org = "test_org"
repoName = "test_repo"
branch = "test_branch"
repoType = "github"
baseUrl = "http://test.gitlab.com"
visibility = "public"
repo = &Repo{
Owner: owner,
Org: org,
Repo: repoName,
Branch: branch,
PathWithNamespace: pathWithNamespace,
RepoType: repoType,
BaseURL: baseUrl,
Visibility: visibility,
}
})

Context("BuildRepoRenderConfig method", func() {
It("should return map", func() {
config := repo.BuildRepoRenderConfig()
appName, ok := config["AppName"]
Expect(ok).Should(BeTrue())
Expect(appName).Should(Equal(repoName))
repoInfo, ok := config["Repo"]
Expect(ok).Should(BeTrue())
repoInfoMap := repoInfo.(map[string]string)
repoName, ok := repoInfoMap["Name"]
Expect(ok).Should(BeTrue())
Expect(repoName).Should(Equal(repoName))
owner, ok := repoInfoMap["Owner"]
Expect(ok).Should(BeTrue())
Expect(owner).Should(Equal(org))
})
})

Context("CreateGithubClient method", func() {
It("should return client", func() {
client, err := repo.CreateGithubClient(false)
Expect(err).Error().ShouldNot(HaveOccurred())
Expect(client).ShouldNot(BeNil())
})
})

//TODO(steinliber) add CreateAndRenderLocalRepo test

Context("getBranch method", func() {
When("branch is not set", func() {
BeforeEach(func() {
repo.Branch = ""
})
It("should return main branch", func() {
repoBranch := repo.getBranch()
Expect(repoBranch).Should(Equal("main"))
})
AfterEach(func() {
repo.Branch = branch
})
})
})

Context("getRepoNameWithBranch method", func() {
It("should return repo and branch name", func() {
repoNameWithURL := repo.getRepoNameWithBranch()
Expect(repoNameWithURL).Should(Equal(fmt.Sprintf("%s-%s", repoName, branch)))
})
})

Context("getRepoOwner method", func() {
When("org is not empty", func() {
It("should return org", func() {
ownerName := repo.getRepoOwner()
Expect(ownerName).Should(Equal(org))
})
})
When("org is empty", func() {
BeforeEach(func() {
repo.Org = ""
})
It("should return owner", func() {
ownerName := repo.getRepoOwner()
Expect(ownerName).Should(Equal(owner))
})
AfterEach(func() {
repo.Org = org
})
})
})
})
Loading