From 0d091217b39a3f5ac5754c71645f14f32543cb2a Mon Sep 17 00:00:00 2001 From: rick <1450685+LinuxSuRen@users.noreply.github.com> Date: Sun, 7 Nov 2021 21:47:29 +0800 Subject: [PATCH] Support set the pre-release action against a alpha version add addtion print columns: phase, version, age remove some unnecessary fields in the bump next version of a releaser --- api/v1alpha1/git_provider_test.go | 17 ++ api/v1alpha1/releaser_types.go | 50 +++++- api/v1alpha1/releaser_types_test.go | 13 ++ api/v1alpha1/releaser_webhook.go | 22 +-- .../bases/devops.kubesphere.io_releasers.yaml | 167 ------------------ controllers/git.go | 42 +++-- controllers/git_test.go | 124 +++++++++++++ controllers/releaser_controller.go | 22 --- controllers/version.go | 19 +- controllers/version_test.go | 48 +++++ 10 files changed, 300 insertions(+), 224 deletions(-) create mode 100644 api/v1alpha1/git_provider_test.go create mode 100644 api/v1alpha1/releaser_types_test.go delete mode 100644 config/crd/bases/devops.kubesphere.io_releasers.yaml diff --git a/api/v1alpha1/git_provider_test.go b/api/v1alpha1/git_provider_test.go new file mode 100644 index 0000000..a41aa99 --- /dev/null +++ b/api/v1alpha1/git_provider_test.go @@ -0,0 +1,17 @@ +package v1alpha1 + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestGitProvider(t *testing.T) { + assert.Equal(t, ProviderUnknown, GetDefaultProvider(&Repository{Address: "https://baidu.com/x/b"})) + assert.Equal(t, ProviderGitHub, GetDefaultProvider(&Repository{Address: "https://github.com/x/b"})) + assert.Equal(t, ProviderGitlab, GetDefaultProvider(&Repository{Address: "https://gitlab.com/x/b"})) + assert.Equal(t, ProviderGitea, GetDefaultProvider(&Repository{Address: "https://gitea.com/x/b"})) + assert.Equal(t, ProviderGitee, GetDefaultProvider(&Repository{Address: "https://gitee.com/x/b"})) + assert.Equal(t, ProviderBitbucket, GetDefaultProvider(&Repository{Address: "https://bitbucket.org/x/b"})) + assert.Equal(t, ProviderGitHub, GetDefaultProvider(&Repository{Provider: ProviderGitHub})) + assert.Equal(t, Provider(""), GetDefaultProvider(&Repository{Address: ""})) +} diff --git a/api/v1alpha1/releaser_types.go b/api/v1alpha1/releaser_types.go index 75e23a2..f5cac89 100644 --- a/api/v1alpha1/releaser_types.go +++ b/api/v1alpha1/releaser_types.go @@ -19,6 +19,7 @@ package v1alpha1 import ( v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "strings" ) // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! @@ -50,8 +51,8 @@ const ( ) // IsValid checks if this is valid -func (p *Phase) IsValid() bool { - switch *p { +func (p Phase) IsValid() bool { + switch p { case PhaseDraft, PhaseReady, PhaseDone: return true default: @@ -61,13 +62,42 @@ func (p *Phase) IsValid() bool { // Repository represents a git repository type Repository struct { - Name string `json:"name"` + // +optional + Name string `json:"name"` + // +optional Provider Provider `json:"provider,omitempty"` Address string `json:"address"` - Branch string `json:"branch,omitempty"` - Version string `json:"version,omitempty"` - Message string `json:"message,omitempty"` - Action Action `json:"action,omitempty"` + // +optional + Branch string `json:"branch,omitempty"` + // +optional + Version string `json:"version,omitempty"` + // +optional + Message string `json:"message,omitempty"` + // +optional + Action Action `json:"action,omitempty"` +} + +// GetDefaultProvider returns the default git provider +func GetDefaultProvider(r *Repository) Provider { + if r.Provider != "" { + return r.Provider + } + + address := r.Address + if strings.HasPrefix(address, "https://github.com/") { + return ProviderGitHub + } else if strings.HasPrefix(address, "https://gitlab.com/") { + return ProviderGitlab + } else if strings.HasPrefix(address, "https://bitbucket.org/") { + return ProviderBitbucket + } else if strings.HasPrefix(address, "https://gitee.com/") { + return ProviderGitee + } else if strings.HasPrefix(address, "https://gitea.com/") { + return ProviderGitea + } else if address != "" { + return ProviderUnknown + } + return "" } // GitOps indicates to integrate with GitOps @@ -93,6 +123,7 @@ const ( type Action string const ( + ActionAuto Action = "auto" ActionTag Action = "tag" ActionPreRelease Action = "pre-release" ActionRelease Action = "release" @@ -114,7 +145,7 @@ type Condition struct { Message string `json:"message"` } -// ConditionType is the type of a condition +// ConditionType is the type of condition type ConditionType string const ( @@ -132,6 +163,9 @@ const ( //+kubebuilder:object:root=true //+kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.spec.phase`,description="The phase of a Releaser" +// +kubebuilder:printcolumn:name="Version",type=string,JSONPath=`.spec.version`,description="The version of a Releaser" +// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`,description="The age of a Releaser" // Releaser is the Schema for the releasers API type Releaser struct { diff --git a/api/v1alpha1/releaser_types_test.go b/api/v1alpha1/releaser_types_test.go new file mode 100644 index 0000000..58eafb1 --- /dev/null +++ b/api/v1alpha1/releaser_types_test.go @@ -0,0 +1,13 @@ +package v1alpha1 + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestPhase(t *testing.T) { + assert.False(t, Phase("unknown").IsValid()) + assert.True(t, Phase("draft").IsValid()) + assert.True(t, Phase("ready").IsValid()) + assert.True(t, Phase("done").IsValid()) +} diff --git a/api/v1alpha1/releaser_webhook.go b/api/v1alpha1/releaser_webhook.go index e6fdbb7..c74c4a5 100644 --- a/api/v1alpha1/releaser_webhook.go +++ b/api/v1alpha1/releaser_webhook.go @@ -23,7 +23,6 @@ import ( ctrl "sigs.k8s.io/controller-runtime" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/webhook" - "strings" ) // log is for logging in this package. @@ -56,17 +55,7 @@ func (r *Releaser) Default() { for i, _ := range r.Spec.Repositories { repo := &r.Spec.Repositories[i] if repo.Provider == "" { - if strings.HasPrefix(repo.Address, "https://github.com/") { - repo.Provider = ProviderGitHub - } else if strings.HasPrefix(repo.Address, "https://gitlab.com/") { - repo.Provider = ProviderGitlab - } else if strings.HasPrefix(repo.Address, "https://bitbucket.org/") { - repo.Provider = ProviderBitbucket - } else if strings.HasPrefix(repo.Address, "https://gitee.com/") { - repo.Provider = ProviderGitee - } else { - repo.Provider = ProviderUnknown - } + repo.Provider = GetDefaultProvider(repo) } if repo.Action == "" { repo.Action = ActionTag @@ -79,8 +68,13 @@ func (r *Releaser) Default() { } } - if r.Spec.GitOps != nil && r.Spec.GitOps.Repository.Branch == "" { - r.Spec.GitOps.Repository.Branch = defaultBranchName + if r.Spec.GitOps != nil { + if r.Spec.GitOps.Repository.Branch == "" { + r.Spec.GitOps.Repository.Branch = defaultBranchName + } + if r.Spec.GitOps.Repository.Provider == "" { + r.Spec.GitOps.Repository.Provider = GetDefaultProvider(&r.Spec.GitOps.Repository) + } } if r.Spec.Secret.Namespace == "" { diff --git a/config/crd/bases/devops.kubesphere.io_releasers.yaml b/config/crd/bases/devops.kubesphere.io_releasers.yaml deleted file mode 100644 index f913615..0000000 --- a/config/crd/bases/devops.kubesphere.io_releasers.yaml +++ /dev/null @@ -1,167 +0,0 @@ - ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.4.1 - creationTimestamp: null - name: releasers.devops.kubesphere.io -spec: - group: devops.kubesphere.io - names: - kind: Releaser - listKind: ReleaserList - plural: releasers - singular: releaser - scope: Namespaced - versions: - - name: v1alpha1 - schema: - openAPIV3Schema: - description: Releaser is the Schema for the releasers API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: ReleaserSpec defines the desired state of Releaser - properties: - gitOps: - description: GitOps indicates to integrate with GitOps - properties: - enable: - type: boolean - repository: - description: Repository represents a git repository - properties: - action: - description: Action indicates the action once the request - phase to be ready - type: string - address: - type: string - branch: - type: string - message: - type: string - name: - type: string - provider: - description: 'Provider represents a git provider, such as: - GitHub, Gitlab' - type: string - version: - type: string - required: - - address - - name - type: object - secret: - description: SecretReference represents a Secret Reference. It - has enough information to retrieve secret in any namespace - properties: - name: - description: Name is unique within a namespace to reference - a secret resource. - type: string - namespace: - description: Namespace defines the space within which the - secret name must be unique. - type: string - type: object - type: object - phase: - description: Phase is the stage of a release request - type: string - repositories: - items: - description: Repository represents a git repository - properties: - action: - description: Action indicates the action once the request phase - to be ready - type: string - address: - type: string - branch: - type: string - message: - type: string - name: - type: string - provider: - description: 'Provider represents a git provider, such as: GitHub, - Gitlab' - type: string - version: - type: string - required: - - address - - name - type: object - type: array - secret: - description: SecretReference represents a Secret Reference. It has - enough information to retrieve secret in any namespace - properties: - name: - description: Name is unique within a namespace to reference a - secret resource. - type: string - namespace: - description: Namespace defines the space within which the secret - name must be unique. - type: string - type: object - version: - type: string - type: object - status: - description: ReleaserStatus defines the observed state of Releaser - properties: - completionTime: - format: date-time - type: string - conditions: - items: - description: Condition indicates the status of each git repositories - properties: - conditionType: - description: ConditionType is the type of a condition - type: string - message: - type: string - status: - description: ConditionStatus is the status of a condition - type: string - required: - - conditionType - - message - - status - type: object - type: array - startTime: - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/controllers/git.go b/controllers/git.go index 6f17783..53f9e42 100644 --- a/controllers/git.go +++ b/controllers/git.go @@ -83,23 +83,15 @@ func release(repo devopsv1alpha1.Repository, secret *v1.Secret, user string) (er token := string(secret.Data[v1.BasicAuthPasswordKey]) server := string(secret.Data["server"]) - var orgAndRepo string - switch repo.Provider { - case devopsv1alpha1.ProviderGitHub: - orgAndRepo = strings.ReplaceAll(repo.Address, "https://github.com/", "") - case devopsv1alpha1.ProviderGitlab: - orgAndRepo = strings.ReplaceAll(repo.Address, "https://gitlab.com/", "") - orgAndRepo = strings.ReplaceAll(orgAndRepo, ".git", "") - case devopsv1alpha1.ProviderGitea: - orgAndRepo = strings.ReplaceAll(repo.Address, server, "") - } + orgAndRepo := getOrgAndRepo(repo, server) provider := internal_scm.GetGitProvider(string(repo.Provider), server, orgAndRepo, token) if provider == nil { return } - switch repo.Action { + action := getAction(repo) + switch action { case devopsv1alpha1.ActionPreRelease: err = provider.Release(repo.Version, repo.Branch, false, true) case devopsv1alpha1.ActionRelease: @@ -108,6 +100,34 @@ func release(repo devopsv1alpha1.Repository, secret *v1.Secret, user string) (er return } +func getOrgAndRepo(repo devopsv1alpha1.Repository, server string) (orgAndRepo string) { + provider := devopsv1alpha1.GetDefaultProvider(&repo) + address := repo.Address + address = strings.ReplaceAll(address, ".git", "") + + switch provider { + case devopsv1alpha1.ProviderGitHub: + orgAndRepo = strings.ReplaceAll(address, "https://github.com/", "") + case devopsv1alpha1.ProviderGitlab: + orgAndRepo = strings.ReplaceAll(address, "https://gitlab.com/", "") + case devopsv1alpha1.ProviderGitee: + orgAndRepo = strings.ReplaceAll(address, "https://gitee.com/", "") + case devopsv1alpha1.ProviderBitbucket: + orgAndRepo = strings.ReplaceAll(address, "https://bitbucket.org/", "") + case devopsv1alpha1.ProviderGitea: + orgAndRepo = strings.ReplaceAll(address, server, "") + } + return +} + +func getAction(repo devopsv1alpha1.Repository) (action devopsv1alpha1.Action) { + action = repo.Action + if action == devopsv1alpha1.ActionAuto && isPreRelease(repo.Version) { + action = devopsv1alpha1.ActionPreRelease + } + return +} + func getAuth(secret *v1.Secret) (auth transport.AuthMethod) { if secret == nil { return diff --git a/controllers/git_test.go b/controllers/git_test.go index ae7ad42..1836852 100644 --- a/controllers/git_test.go +++ b/controllers/git_test.go @@ -3,6 +3,7 @@ package controllers import ( "github.com/go-git/go-git/v5/plumbing/transport" "github.com/go-git/go-git/v5/plumbing/transport/ssh" + "github.com/kubesphere-sigs/ks-releaser/api/v1alpha1" "github.com/stretchr/testify/assert" v1 "k8s.io/api/core/v1" "testing" @@ -42,3 +43,126 @@ func TestGetAuth(t *testing.T) { assert.NotNil(t, auth) assert.Equal(t, ssh.PublicKeysName, auth.Name()) } + +func Test_getAction(t *testing.T) { + type args struct { + repo v1alpha1.Repository + } + tests := []struct { + name string + args args + wantAction v1alpha1.Action + }{{ + name: "auto action", + args: args{ + repo: v1alpha1.Repository{ + Action: v1alpha1.ActionAuto, + Version: "v1.2.3-alpha.1", + }, + }, + wantAction: v1alpha1.ActionPreRelease, + }, { + name: "release action", + args: args{ + repo: v1alpha1.Repository{ + Action: v1alpha1.ActionRelease, + Version: "v1.2.3-alpha.1", + }, + }, + wantAction: v1alpha1.ActionRelease, + }, { + name: "tag action", + args: args{ + repo: v1alpha1.Repository{ + Action: v1alpha1.ActionTag, + Version: "v1.2.3-alpha.1", + }, + }, + wantAction: v1alpha1.ActionTag, + }, { + name: "pre-release action", + args: args{ + repo: v1alpha1.Repository{ + Action: v1alpha1.ActionPreRelease, + Version: "v1.2.3-alpha.1", + }, + }, + wantAction: v1alpha1.ActionPreRelease, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if gotAction := getAction(tt.args.repo); gotAction != tt.wantAction { + t.Errorf("getAction() = %v, want %v", gotAction, tt.wantAction) + } + }) + } +} + +func Test_getOrgAndRepo(t *testing.T) { + type args struct { + repo v1alpha1.Repository + server string + } + tests := []struct { + name string + args args + wantOrgAndRepo string + }{{ + name: "github with http protocol", + args: args{ + repo: v1alpha1.Repository{ + Address: "https://github.com/x/b", + }, + }, + wantOrgAndRepo: "x/b", + }, { + name: "github with git protocol", + args: args{ + repo: v1alpha1.Repository{ + Address: "https://github.com/x/b.git", + }, + }, + wantOrgAndRepo: "x/b", + }, { + name: "gitlab with git protocol", + args: args{ + repo: v1alpha1.Repository{ + Address: "https://gitlab.com/x/b.git", + }, + }, + wantOrgAndRepo: "x/b", + }, { + name: "gitee with git protocol", + args: args{ + repo: v1alpha1.Repository{ + Address: "https://gitee.com/x/b.git", + }, + }, + wantOrgAndRepo: "x/b", + }, { + name: "bitbucket with git protocol", + args: args{ + repo: v1alpha1.Repository{ + Address: "https://bitbucket.org/x/b.git", + }, + }, + wantOrgAndRepo: "x/b", + }, { + name: "gitea with git protocol", + args: args{ + repo: v1alpha1.Repository{ + Provider: v1alpha1.ProviderGitea, + Address: "https://localhost/x/b.git", + }, + server: "https://localhost/", + }, + wantOrgAndRepo: "x/b", + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if gotOrgAndRepo := getOrgAndRepo(tt.args.repo, tt.args.server); gotOrgAndRepo != tt.wantOrgAndRepo { + t.Errorf("getOrgAndRepo() = %v, want %v", gotOrgAndRepo, tt.wantOrgAndRepo) + } + }) + } +} diff --git a/controllers/releaser_controller.go b/controllers/releaser_controller.go index dda7494..9c26643 100644 --- a/controllers/releaser_controller.go +++ b/controllers/releaser_controller.go @@ -220,28 +220,6 @@ func (r *ReleaserReconciler) markAsDone(secret *v1.Secret, releaser *devopsv1alp bumpFilename = path.Join(dir, bumpFilename) err = saveAndPush(gitRepo, r.gitUser, bumpFilename, data, secret) } - - //if data, err = ioutil.ReadFile(filePath); err == nil { - // data, err = updateReleaserAsYAML(data, func(releaser *devopsv1alpha1.Releaser) { - // releaser.Spec.Phase = devopsv1alpha1.PhaseDone - // }) - // if err == nil { - // r.logger.Info("start to commit phase to be done", "name", releaser.Name) - // if err = saveAndPush(gitRepo, r.gitUser, filePath, data, secret); err != nil { - // err = fmt.Errorf("failed to write file %s, error: %v", filePath, err) - // return - // } - // - // r.logger.Info("start to create next release file") - // var bumpFilename string - // if data, bumpFilename, err = bumpReleaserAsData(data); err != nil { - // err = fmt.Errorf("failed to bump releaser: %s, error: %v", filePath, err) - // } else { - // bumpFilename = path.Join(dir, bumpFilename) - // err = saveAndPush(gitRepo, r.gitUser, bumpFilename, data, secret) - // } - // } - //} return } diff --git a/controllers/version.go b/controllers/version.go index bcab818..0538e91 100644 --- a/controllers/version.go +++ b/controllers/version.go @@ -8,6 +8,13 @@ import ( "strings" ) +func isPreRelease(versionStr string) bool { + if version, err := semver.ParseTolerant(versionStr); err == nil { + return len(version.Pre) > 0 + } + return false +} + func bumpVersion(versionStr string) (nextVersion string, err error) { nextVersion = versionStr // keep using the old version if there's any problem happened @@ -18,10 +25,10 @@ func bumpVersion(versionStr string) (nextVersion string, err error) { } if preVersionCount := len(version.Pre); preVersionCount > 0 { - for i := preVersionCount -1; i >= 0; i-- { + for i := preVersionCount - 1; i >= 0; i-- { preVersion := &version.Pre[i] if preVersion.IsNumeric() { - preVersion.VersionNum+=1 + preVersion.VersionNum += 1 break } } @@ -44,6 +51,11 @@ func bumpReleaser(releaser *devopsv1alpha1.Releaser) { releaser.Name = nameWithoutVersion + nextVersion } + // remove the metadata + releaser.ObjectMeta.Generation = 0 + releaser.ObjectMeta.SelfLink = "" + releaser.ObjectMeta.Annotations = nil + releaser.Spec.Phase = devopsv1alpha1.PhaseDraft releaser.Spec.Version = nextVersion @@ -51,6 +63,9 @@ func bumpReleaser(releaser *devopsv1alpha1.Releaser) { repo := &releaser.Spec.Repositories[i] repo.Version, _ = bumpVersion(repo.Version) } + + // remove status + releaser.Status = devopsv1alpha1.ReleaserStatus{} return } diff --git a/controllers/version_test.go b/controllers/version_test.go index 54aca52..7f72c5c 100644 --- a/controllers/version_test.go +++ b/controllers/version_test.go @@ -191,3 +191,51 @@ status: {} }) } } + +func Test_isPreRelease(t *testing.T) { + type args struct { + versionStr string + } + tests := []struct { + name string + args args + want bool + }{{ + name: "normal release version", + args: args{ + versionStr: "v1.2.3", + }, + want: false, + }, { + name: "alpha version", + args: args{ + versionStr: "v1.2.3-alpha.1", + }, + want: true, + }, { + name: "beta version", + args: args{ + versionStr: "v1.2.3-beta.1", + }, + want: true, + }, { + name: "rc version", + args: args{ + versionStr: "v1.2.3-rc.1", + }, + want: true, + }, { + name: "invalid version number", + args: args{ + versionStr: "fake-version", + }, + want: false, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := isPreRelease(tt.args.versionStr); got != tt.want { + t.Errorf("isPreRelease() = %v, want %v", got, tt.want) + } + }) + } +}