Skip to content

Commit

Permalink
Merge pull request #17 from kubesphere-sigs/auto-bump-pr
Browse files Browse the repository at this point in the history
Support create a draft config automatically once it was released
  • Loading branch information
LinuxSuRen authored Nov 4, 2021
2 parents 2cd766b + 7540ab0 commit 8cf4ea3
Show file tree
Hide file tree
Showing 13 changed files with 606 additions and 283 deletions.
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and Cust
generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations.
$(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..."

goreleaser:
goreleaser release --rm-dist --snapshot

fmt: ## Run go fmt against code.
go fmt ./...

Expand Down Expand Up @@ -86,7 +89,8 @@ deploy-without-update:

undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config.
$(KUSTOMIZE) build config/default | kubectl delete -f -

undeploy-without-update:
kustomize build config/default | kubectl delete -f -

CONTROLLER_GEN = $(shell pwd)/bin/controller-gen
controller-gen: ## Download controller-gen locally if necessary.
Expand Down
36 changes: 26 additions & 10 deletions api/v1alpha1/releaser_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ const (
func (p *Phase) IsValid() bool {
switch *p {
case PhaseDraft, PhaseReady, PhaseDone:
return true
return true
default:
return false
}
Expand All @@ -80,11 +80,11 @@ type GitOps struct {
type Provider string

const (
ProviderGitHub Provider = "github"
ProviderGitlab Provider = "gitlab"
ProviderGitHub Provider = "github"
ProviderGitlab Provider = "gitlab"
ProviderBitbucket Provider = "bitbucket"
ProviderGitee Provider = "gitee"
ProviderUnknown Provider = "unknown"
ProviderGitee Provider = "gitee"
ProviderUnknown Provider = "unknown"
)

// Action indicates the action once the request phase to be ready
Expand All @@ -107,21 +107,37 @@ type ReleaserStatus struct {

// Condition indicates the status of each git repositories
type Condition struct {
RepositoryName string `json:"repositoryName"`
Status string `json:"status"`
Message string `json:"message"`
ConditionType ConditionType `json:"conditionType"`
Status ConditionStatus `json:"status"`
Message string `json:"message"`
}

// ConditionType is the type of a condition
type ConditionType string

const (
ConditionTypeRelease ConditionType = "release"
ConditionTypeOther ConditionType = "other"
)

// ConditionStatus is the status of a condition
type ConditionStatus string

const (
ConditionStatusSuccess ConditionStatus = "success"
ConditionStatusFailed ConditionStatus = "failed"
)

//+kubebuilder:object:root=true
//+kubebuilder:subresource:status

// Releaser is the Schema for the releasers API
type Releaser struct {
metav1.TypeMeta `json:",inline"`
metav1.TypeMeta `json:",inline"`
// +optional
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec ReleaserSpec `json:"spec,omitempty"`
Spec ReleaserSpec `json:"spec,omitempty"`
// +optional
Status ReleaserStatus `json:"status,omitempty"`
}
Expand Down
8 changes: 5 additions & 3 deletions config/crd/bases/devops.kubesphere.io_releasers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -123,15 +123,17 @@ spec:
items:
description: Condition indicates the status of each git repositories
properties:
message:
conditionType:
description: ConditionType is the type of a condition
type: string
repositoryName:
message:
type: string
status:
description: ConditionStatus is the status of a condition
type: string
required:
- conditionType
- message
- repositoryName
- status
type: object
type: array
Expand Down
4 changes: 2 additions & 2 deletions config/samples/devops_v1alpha1_releaser.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
apiVersion: devops.kubesphere.io/v1alpha1
kind: Releaser
metadata:
name: releaser-sample-4
name: releaser-sample-v0.0.9
spec:
version: v0.0.4
gitOps:
Expand All @@ -15,7 +15,7 @@ spec:
address: https://github.com/linuxsuren-bot/test
action: release
branch: master
version: v0.0.4
version: v0.0.8
provider: github
secret:
name: test-git
Expand Down
240 changes: 240 additions & 0 deletions controllers/git.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
package controllers

import (
"errors"
"fmt"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/go-git/go-git/v5/plumbing/storer"
"github.com/go-git/go-git/v5/plumbing/transport"
devopsv1alpha1 "github.com/kubesphere-sigs/ks-releaser/api/v1alpha1"
"github.com/kubesphere-sigs/ks-releaser/controllers/internal_scm"
"golang.org/x/crypto/ssh"
githttp "gopkg.in/src-d/go-git.v4/plumbing/transport/http"
gitssh "gopkg.in/src-d/go-git.v4/plumbing/transport/ssh"
"io/ioutil"
v1 "k8s.io/api/core/v1"
"net/url"
"os"
"path"
"strings"
"time"
)

/**
TODO make these functions into a struct
For example, we can share parts of the variables, such as git.Repository, secret .etc.
*/

func saveAndPush(gitRepo *git.Repository, user, targetFile string, data []byte, secret *v1.Secret) (err error) {
if err = ioutil.WriteFile(targetFile, data, 0644); err != nil {
fmt.Println("failed to write file", targetFile)
} else {
if err = addAndCommit(gitRepo, user); err == nil {
err = pushTags(gitRepo, "", getAuth(secret))
}
}
return
}

func addAndCommit(repo *git.Repository, user string) (err error) {
var w *git.Worktree
if w, err = repo.Worktree(); err == nil {
_, _ = w.Add(".")
var commit plumbing.Hash
commit, err = w.Commit("example go-git commit", &git.CommitOptions{
All: true,
Author: &object.Signature{
Name: user,
Email: fmt.Sprintf("%[email protected]", user),
When: time.Now(),
},
})

if err == nil {
_, err = repo.CommitObject(commit)
}
}
return
}

func release(repo devopsv1alpha1.Repository, secret *v1.Secret, user string) (err error) {
auth := getAuth(secret)

var gitRepo *git.Repository
if gitRepo, err = clone(repo.Address, repo.Branch, auth, "tmp"); err != nil {
return
}

if repo.Message == "" {
repo.Message = "released by ks-releaser"
}
if _, err = setTag(gitRepo, repo.Version, repo.Message, user); err != nil {
return
}

if err = pushTags(gitRepo, repo.Version, auth); err != nil {
return
}

var orgAndRepo string
switch repo.Provider {
case devopsv1alpha1.ProviderGitHub:
orgAndRepo = strings.ReplaceAll(repo.Address, "https://github.com/", "")
}

switch repo.Action {
case devopsv1alpha1.ActionPreRelease:
provider := internal_scm.GetGitProvider(string(repo.Provider), orgAndRepo, string(secret.Data[v1.BasicAuthPasswordKey]))
if provider != nil {
err = provider.Release(repo.Version, false, true)
}
case devopsv1alpha1.ActionRelease:
provider := internal_scm.GetGitProvider(string(repo.Provider), orgAndRepo, string(secret.Data[v1.BasicAuthPasswordKey]))
if provider != nil {
err = provider.Release(repo.Version, false, false)
}
}
return
}

func getAuth(secret *v1.Secret) (auth transport.AuthMethod) {
switch secret.Type {
case v1.SecretTypeBasicAuth:
auth = &githttp.BasicAuth{
Username: string(secret.Data[v1.BasicAuthUsernameKey]),
Password: string(secret.Data[v1.BasicAuthPasswordKey]),
}
case v1.SecretTypeSSHAuth:
signer, _ := ssh.ParsePrivateKey(secret.Data[v1.SSHAuthPrivateKey])
auth = &gitssh.PublicKeys{User: "git", Signer: signer}
}
return
}

func clone(gitRepo, branch string, auth transport.AuthMethod, cacheDir string) (repo *git.Repository, err error) {
var gitRepoURL *url.URL
if gitRepoURL, err = url.Parse(gitRepo); err != nil {
return
}

dir := path.Join(cacheDir, gitRepoURL.Path)
if ok, _ := PathExists(dir); ok {
if repo, err = git.PlainOpen(dir); err == nil {
var wd *git.Worktree

if wd, err = repo.Worktree(); err == nil {
if err = wd.Checkout(&git.CheckoutOptions{
Branch: plumbing.NewBranchReferenceName(branch),
Create: false,
Force: true,
}); err != nil {
err = fmt.Errorf("unable to checkout git branch: %s", branch)
return
}

if err = wd.Pull(&git.PullOptions{
Progress: os.Stdout,
ReferenceName: plumbing.NewBranchReferenceName(branch),
Force: true, // in case of the force pushing
Auth: auth,
}); err != nil && err != git.NoErrAlreadyUpToDate {
err = fmt.Errorf("failed to pull git repository '%s', error: %v", repo, err)
} else {
err = nil
}
}
} else {
err = fmt.Errorf("failed to open git local repository, error: %v", err)
}
} else {
repo, err = git.PlainClone(dir, false, &git.CloneOptions{
URL: gitRepo,
ReferenceName: plumbing.NewBranchReferenceName(branch),
Progress: os.Stdout,
Auth: auth,
})
}
return
}

func tagExists(tag string, r *git.Repository) bool {
var err error
var tags storer.ReferenceIter
if tags, err = r.Tags(); err == nil {
err = tags.ForEach(func(reference *plumbing.Reference) error {
tagRef := reference.Name()
if tagRef.IsTag() && !tagRef.IsRemote() {
_ = r.DeleteTag(tag)
} else if tagRef.IsTag() && tagRef.IsRemote() && tagRef.String() == tag {
return nil
}
return errors.New("not found tag")
})
}
return err == nil || (err != nil && err.Error() != "not found tag")
}

func setTag(r *git.Repository, tag, message, user string) (bool, error) {
if tagExists(tag, r) {
fmt.Printf("tag %s already exists\n", tag)
return false, nil
}
fmt.Printf("Set tag %s\n", tag)
h, err := r.Head()
if err != nil {
fmt.Printf("get HEAD error: %s\n", err)
return false, err
}
_, err = r.CreateTag(tag, h.Hash(), &git.CreateTagOptions{
Tagger: &object.Signature{
Name: user,
Email: fmt.Sprintf("%[email protected]", user),
When: time.Time{},
},
Message: message,
})

if err != nil {
fmt.Printf("create tag error: %s\n", err)
return false, err
}
return true, nil
}

func pushTags(r *git.Repository, tag string, auth transport.AuthMethod) (err error) {
var ref []config.RefSpec
if tag != "" {
ref = []config.RefSpec{config.RefSpec(fmt.Sprintf("refs/tags/%s:refs/tags/%s", tag, tag))}
}

po := &git.PushOptions{
RemoteName: "origin",
Progress: os.Stdout,
RefSpecs: ref,
Auth: auth,
}
if err = r.Push(po); err != nil {
if err == git.NoErrAlreadyUpToDate {
fmt.Print("origin remote was up to date, no push done\n")
err = nil
return
}
err = fmt.Errorf("push to remote origin error: %s\n", err)
}
return
}

// PathExists checks if the target path exist or not
func PathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
1 change: 1 addition & 0 deletions controllers/internal_scm/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ type GitReleaser interface {
Release(version string, draft, prerelease bool) (err error)
}

// GetGitProvider returns the GitReleaser implement by kind
func GetGitProvider(kind, repo, token string) GitReleaser {
switch v1alpha1.Provider(kind) {
case v1alpha1.ProviderGitHub:
Expand Down
Loading

0 comments on commit 8cf4ea3

Please sign in to comment.