fix dns, build image and run image

This commit is contained in:
Lunny Xiao 2021-10-29 09:57:53 +08:00
parent df4bfd8517
commit e143009677
No known key found for this signature in database
GPG Key ID: C3B7C91B632F738A
9 changed files with 244 additions and 200 deletions

25
pkgs/services/client.go Normal file
View File

@ -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)
}

View File

@ -1 +0,0 @@
package services

18
pkgs/services/cmd.go Normal file
View File

@ -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
}

View File

@ -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
}

58
pkgs/services/dns.go Normal file
View File

@ -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
}

80
pkgs/services/git.go Normal file
View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)