From e143009677164e7fbe6fc9712d7fb31b133193cb Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 29 Oct 2021 09:57:53 +0800 Subject: [PATCH] fix dns, build image and run image --- pkgs/services/client.go | 25 +++++ pkgs/services/cloudflare.go | 1 - pkgs/services/cmd.go | 18 ++++ pkgs/services/container.go | 43 ++++++++ pkgs/services/dns.go | 58 ++++++++++ pkgs/services/git.go | 80 ++++++++++++++ pkgs/services/github.go | 6 -- pkgs/services/services.go | 207 +++--------------------------------- pkgs/settings/settings.go | 6 ++ 9 files changed, 244 insertions(+), 200 deletions(-) create mode 100644 pkgs/services/client.go delete mode 100644 pkgs/services/cloudflare.go create mode 100644 pkgs/services/cmd.go create mode 100644 pkgs/services/container.go create mode 100644 pkgs/services/dns.go create mode 100644 pkgs/services/git.go diff --git a/pkgs/services/client.go b/pkgs/services/client.go new file mode 100644 index 0000000..e9e04c6 --- /dev/null +++ b/pkgs/services/client.go @@ -0,0 +1,25 @@ +package services + +import ( + "context" + "fmt" + + "gitea.com/gitea/pr-deployer/pkgs/settings" + + "github.com/google/go-github/v39/github" + "golang.org/x/oauth2" +) + +type Client interface { + CheckWebhook(ctx context.Context) error + UpdateCommitStatus(ctx context.Context, number int, sha, status, desc, targetURL string) error + GetPullRequests(p int) ([]*github.PullRequest, error) +} + +func NewClient(token *oauth2.Token) (Client, error) { + if settings.ServiceType == "github" { + return NewGithubClient(token), nil + } + + return nil, fmt.Errorf("unsupported service type: %s", settings.ServiceType) +} diff --git a/pkgs/services/cloudflare.go b/pkgs/services/cloudflare.go deleted file mode 100644 index 5e568ea..0000000 --- a/pkgs/services/cloudflare.go +++ /dev/null @@ -1 +0,0 @@ -package services diff --git a/pkgs/services/cmd.go b/pkgs/services/cmd.go new file mode 100644 index 0000000..8dbb65e --- /dev/null +++ b/pkgs/services/cmd.go @@ -0,0 +1,18 @@ +package services + +import ( + "os/exec" + "path/filepath" + "strconv" + + "gitea.com/gitea/pr-deployer/pkgs/settings" +) + +func getPRDir(number int) string { + return filepath.Join(settings.CodeCacheDir, strconv.Itoa(number)) +} + +func WithDir(cmd *exec.Cmd, dir string) *exec.Cmd { + cmd.Dir = dir + return cmd +} diff --git a/pkgs/services/container.go b/pkgs/services/container.go new file mode 100644 index 0000000..4abd30c --- /dev/null +++ b/pkgs/services/container.go @@ -0,0 +1,43 @@ +package services + +import ( + "bytes" + "context" + "fmt" + "os/exec" + + "gitea.com/gitea/pr-deployer/pkgs/settings" + + "github.com/pkg/errors" +) + +func getImageTag(number int) string { + return fmt.Sprintf("pr-%d", number) +} + +// buildImage build the docker image via Dockerfile +func buildImage(ctx context.Context, number int) (string, error) { + p := getPRDir(number) + tagName := fmt.Sprintf("%s/%s:%s", settings.RepoOwner, settings.RepoName, getImageTag(number)) + cmd := exec.Command("docker", "build", "-t", + tagName, + ".", + ) + if err := WithDir(cmd, p).Run(); err != nil { + return "", errors.Wrap(err, "docker build") + } + + return tagName, nil +} + +func runImage(ctx context.Context, number int, image string) (string, error) { + p := getPRDir(number) + var buf bytes.Buffer + cmd := exec.Command("docker", "run", "-d", image) + cmd.Stdout = &buf + if err := WithDir(cmd, p).Run(); err != nil { + return "", errors.Wrap(err, "docker run") + } + + return buf.String(), nil +} diff --git a/pkgs/services/dns.go b/pkgs/services/dns.go new file mode 100644 index 0000000..7ff826b --- /dev/null +++ b/pkgs/services/dns.go @@ -0,0 +1,58 @@ +package services + +import ( + "context" + "fmt" + + "gitea.com/gitea/pr-deployer/pkgs/settings" + "github.com/cloudflare/cloudflare-go" + "github.com/google/go-github/v39/github" +) + +func checkAndUpdateSubDomain(number int) error { + api, err := cloudflare.NewWithAPIToken(settings.CloudflareToken) + if err != nil { + return err + } + + zoneID, err := api.ZoneIDByName(settings.PRParentDomain) + if err != nil { + return fmt.Errorf("ZoneIDByName: %v", err) + } + + var found bool + var name = fmt.Sprintf("try-pr-%d", number) + var filter = cloudflare.DNSRecord{ + Type: "A", + Name: name + "." + settings.PRParentDomain, + } + recs, err := api.DNSRecords(context.Background(), zoneID, filter) + if err != nil { + return fmt.Errorf("DNSRecords: %v", err) + } + + for _, r := range recs { + if filter.Name == r.Name { + found = true + break + } + } + if found { + // TODO: check ip address + return nil + } + + asciiInput := cloudflare.DNSRecord{ + Type: "A", + Name: name, + Content: settings.DomainIP, + Proxiable: false, + Proxied: github.Bool(false), + } + + _, err = api.CreateDNSRecord(context.Background(), zoneID, asciiInput) + if err != nil { + return fmt.Errorf("CreateDNSRecord: %v", err) + } + return nil +} diff --git a/pkgs/services/git.go b/pkgs/services/git.go new file mode 100644 index 0000000..2258caa --- /dev/null +++ b/pkgs/services/git.go @@ -0,0 +1,80 @@ +package services + +import ( + "bytes" + "fmt" + "os" + "os/exec" + "strings" + + "gitea.com/gitea/pr-deployer/pkgs/settings" + + "github.com/pkg/errors" + log "github.com/sirupsen/logrus" +) + +func updateGitRepo(number int, sha string) (string, error) { + p := getPRDir(number) + log.Trace("clone code into", p) + + var local = fmt.Sprintf("pull/%d/head:pr/%d", number, number) + st, err := os.Stat(p) + if err != nil { + if !os.IsNotExist(err) { + return "", err + } + + if err := exec.Command("git", "clone", settings.RepoURL, p).Run(); err != nil { + return "", fmt.Errorf("git clone: %v", err) + } + + cmd := exec.Command("git", "fetch", "origin", local) + if err := WithDir(cmd, p).Run(); err != nil { + return "", errors.Wrap(err, "fetch") + } + } else if !st.IsDir() { + return "", fmt.Errorf("%s is a file but not a dir", p) + } + + cmd := exec.Command("git", "pull", "origin", local) + if err := WithDir(cmd, p).Run(); err != nil { + return "", errors.Wrap(err, fmt.Sprintf("checkout %s", sha)) + } + + var branchName = fmt.Sprintf("pr/%d", number) + var newSHA = sha + + if sha != "" { + cmd := exec.Command("git", "checkout", sha) + if err := WithDir(cmd, p).Run(); err != nil { + return "", errors.Wrap(err, fmt.Sprintf("checkout %s", sha)) + } + newSHA = sha + } else { + cmd := exec.Command("git", "rev-parse", "--abbrev-ref", "HEAD") + cmd.Dir = p + var buf bytes.Buffer + cmd.Stdout = &buf + if err := cmd.Run(); err != nil { + return "", fmt.Errorf("rev-parse --abbrev-ref: %v", err) + } + + if buf.String() != branchName { + cmd := exec.Command("git", "checkout", branchName) + if err := WithDir(cmd, p).Run(); err != nil { + return "", fmt.Errorf("git checkout %s: %v", branchName, err) + } + } + + cmd = exec.Command("git", "rev-parse", "HEAD") + cmd.Dir = p + var buf2 bytes.Buffer + cmd.Stdout = &buf2 + if err := cmd.Run(); err != nil { + return "", fmt.Errorf("git rev-parse Head: %v", err) + } + newSHA = strings.TrimSpace(buf2.String()) + } + + return newSHA, nil +} diff --git a/pkgs/services/github.go b/pkgs/services/github.go index 9343d0e..4af91d2 100644 --- a/pkgs/services/github.go +++ b/pkgs/services/github.go @@ -18,12 +18,6 @@ import ( "golang.org/x/oauth2" ) -type Client interface { - CheckWebhook(ctx context.Context) error - UpdateCommitStatus(ctx context.Context, number int, sha, status, desc, targetURL string) error - GetPullRequests(p int) ([]*github.PullRequest, error) -} - type GithubClient struct { client *github.Client } diff --git a/pkgs/services/services.go b/pkgs/services/services.go index 4c552f3..e7949f0 100644 --- a/pkgs/services/services.go +++ b/pkgs/services/services.go @@ -1,24 +1,18 @@ package services import ( - "bytes" "context" "fmt" - "io" - "os" - "os/exec" - "path/filepath" - "strconv" - "strings" "gitea.com/gitea/pr-deployer/pkgs/settings" - - "github.com/cloudflare/cloudflare-go" - "github.com/docker/docker/api/types" - "github.com/docker/docker/client" - "github.com/pkg/errors" log "github.com/sirupsen/logrus" - "golang.org/x/oauth2" +) + +const ( + statusPending = "pending" + statusSuccess = "success" + statusError = "error" + statusFailure = "failure" ) func UpdateAndStartPullRequest(ctx context.Context, client Client, number int, sha string) error { @@ -59,200 +53,27 @@ func updateAndStartPullRequest(ctx context.Context, client Client, number int, n log.Trace("buildImage") // 3 build - if err := buildImage(ctx, number); err != nil { + image, err := buildImage(ctx, number) + if err != nil { return err } - log.Trace("runImage") + log.Trace("runImage", image) // 4 change reverse server - if err := runImage(ctx, number); err != nil { + runID, err := runImage(ctx, number, image) + if err != nil { return err } - log.Trace("UpdateCommitStatus success", newSHA) + log.Trace("UpdateCommitStatus success", newSHA, runID) // 5 send commit status - if err := client.UpdateCommitStatus(ctx, number, newSHA, statusSuccess, "", fmt.Sprintf("https://try-pr-%d.gitea.io", number)); err != nil { + if err := client.UpdateCommitStatus(ctx, number, newSHA, statusSuccess, "", fmt.Sprintf("https://try-pr-%d.%s", number, settings.PRParentDomain)); err != nil { return err } return nil } -func runImage(ctx context.Context, number int) error { - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - return err - } - - // TODO: stop the old container - - // start the new container - _, err = cli.ContainerExecCreate(ctx, fmt.Sprintf("%s/%s:%s", settings.RepoOwner, settings.RepoName, getImageTag(number)), types.ExecConfig{ - WorkingDir: getPRDir(number), - }) - return err -} - -const ( - statusPending = "pending" - statusSuccess = "success" - statusError = "error" - statusFailure = "failure" -) - -func getPRDir(number int) string { - return filepath.Join(settings.CodeCacheDir, strconv.Itoa(number)) -} - -func WithDir(cmd *exec.Cmd, dir string) *exec.Cmd { - cmd.Dir = dir - return cmd -} - -func updateGitRepo(number int, sha string) (string, error) { - p := getPRDir(number) - log.Trace("clone code into", p) - - var local = fmt.Sprintf("pull/%d/head:pr/%d", number, number) - st, err := os.Stat(p) - if err != nil { - if os.IsNotExist(err) { - if err := exec.Command("git", "clone", settings.RepoURL, p).Run(); err != nil { - return "", fmt.Errorf("git clone: %v", err) - } - - cmd := exec.Command("git", "fetch", "origin", local) - if err := WithDir(cmd, p).Run(); err != nil { - return "", errors.Wrap(err, "fetch") - } - } - return "", err - } - if !st.IsDir() { - return "", fmt.Errorf("%s is a file but not a dir", p) - } - - cmd := exec.Command("git", "pull", "origin", local) - if err := WithDir(cmd, p).Run(); err != nil { - return "", errors.Wrap(err, fmt.Sprintf("checkout %s", sha)) - } - - var branchName = fmt.Sprintf("pr/%d", number) - var newSHA = sha - - if sha != "" { - cmd := exec.Command("git", "checkout", sha) - if err := WithDir(cmd, p).Run(); err != nil { - return "", errors.Wrap(err, fmt.Sprintf("checkout %s", sha)) - } - newSHA = sha - } else { - cmd := exec.Command("git", "rev-parse", "--abbrev-ref", "HEAD") - cmd.Dir = p - var buf bytes.Buffer - cmd.Stdout = &buf - if err := cmd.Run(); err != nil { - return "", fmt.Errorf("rev-parse --abbrev-ref: %v", err) - } - - if buf.String() != branchName { - cmd := exec.Command("git", "checkout", branchName) - if err := WithDir(cmd, p).Run(); err != nil { - return "", fmt.Errorf("git checkout %s: %v", branchName, err) - } - } - - cmd = exec.Command("git", "rev-parse", "HEAD") - cmd.Dir = p - var buf2 bytes.Buffer - cmd.Stdout = &buf2 - if err := cmd.Run(); err != nil { - return "", fmt.Errorf("git rev-parse Head: %v", err) - } - newSHA = strings.TrimSpace(buf2.String()) - } - - return newSHA, nil -} - -func getImageTag(number int) string { - return fmt.Sprintf("pr-%d", number) -} - -// buildImage build the docker image via Dockerfile -func buildImage(ctx context.Context, number int) error { - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - return err - } - - var buildContext io.Reader - if _, err = cli.ImageBuild(ctx, buildContext, types.ImageBuildOptions{ - Tags: []string{getImageTag(number)}, - }); err != nil { - return err - } - - return nil -} - -func checkAndUpdateSubDomain(number int) error { - api, err := cloudflare.NewWithAPIToken(settings.CloudflareToken) - if err != nil { - return err - } - - zoneID, err := api.ZoneIDByName("gitea.io") - if err != nil { - return err - } - - var found bool - var name = fmt.Sprintf("try-pr-%d.gitea.io", number) - foo := cloudflare.DNSRecord{ - Type: "name", - Name: name, - } - recs, err := api.DNSRecords(context.Background(), zoneID, foo) - if err != nil { - return err - } - - for _, r := range recs { - if name == r.Name { - found = true - break - } - } - if found { - // check ip address - return nil - } - - asciiInput := cloudflare.DNSRecord{ - Type: "A", - Name: name, - Content: settings.DomainIP, - TTL: 120, - //Priority: &priority, - //Proxied: &proxied, - } - - _, err = api.CreateDNSRecord(context.Background(), zoneID, asciiInput) - if err != nil { - return err - } - return nil -} - func StopPullRequest(number int) error { return nil } - -func NewClient(token *oauth2.Token) (Client, error) { - if settings.ServiceType == "github" { - return NewGithubClient(token), nil - } - - return nil, fmt.Errorf("unsupported service type: %s", settings.ServiceType) -} diff --git a/pkgs/settings/settings.go b/pkgs/settings/settings.go index a56ea88..c569d39 100644 --- a/pkgs/settings/settings.go +++ b/pkgs/settings/settings.go @@ -24,6 +24,7 @@ var ( OAuth2ClientSecret string Domain string DomainIP string + PRParentDomain string WebhookSecretKey []byte CodeCacheDir string CloudflareToken string @@ -104,6 +105,11 @@ func Init() error { Domain = viper.GetString("Domain") DomainIP = viper.GetString("DomainIP") + PRParentDomain = viper.GetString("PRParentDomain") + if PRParentDomain == "" { + return errors.New("PRParentDomain should not be empty") + } + WebhookSecretKey = []byte(viper.GetString("WebhookSecretKey")) CodeCacheDir = viper.GetString("CodeCacheDir") CodeCacheDir, err = filepath.Abs(CodeCacheDir)