diff --git a/.golangci.yaml b/.golangci.yaml index 8c69e9896..dc3be253d 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -6,17 +6,20 @@ linters: - bidichk - bodyclose - containedctx - # - depguard - - dogsled + - contextcheck - durationcheck - - errcheck - errchkjson + - errname + - errorlint + - exhaustive - exportloopref + - forcetypeassert - gci - goconst - gocritic - godot - gofmt + - gofumpt - goimports - goprintffuncname - gosec @@ -25,37 +28,29 @@ linters: - importas - ineffassign - loggercheck + - makezero - misspell - nakedret - nilerr - noctx - nolintlint + - nosprintfhostport - prealloc - predeclared + - reassign - revive - rowserrcheck - staticcheck - stylecheck + - tagliatelle - thelper + - tparallel - typecheck - unconvert - - unparam - - unused - - whitespace - - wrapcheck - - contextcheck - - errname - - errorlint - - exhaustive - - forcetypeassert - - gofmt - - gofumpt - - makezero - - nosprintfhostport - - tagliatelle - - tparallel - usestdlibvars + - unused - wastedassign + - wrapcheck linters-settings: godot: @@ -85,58 +80,27 @@ linters-settings: # Controller Runtime - pkg: sigs.k8s.io/controller-runtime alias: ctrl + gofumpt: + extra-rules: true nolintlint: allow-unused: false allow-leading-space: false require-specific: true staticcheck: - go: "1.20" + go: "1.21" stylecheck: - go: "1.20" + go: "1.21" checks: ["all", "-ST1006"] dot-import-whitelist: - "github.com/onsi/gomega" - "github.com/onsi/ginkgo/v2" - gosec: - excludes: - - G307 # Deferring unsafe method "Close" on type "\*os.File" - - G108 # Profiling endpoint is automatically exposed on /debug/pprof - - G106 # Allowing for insecure ssh gocritic: enabled-tags: - diagnostic - - experimental + - style - performance - disabled-checks: - - appendAssign - - dupImport # https://github.com/go-critic/go-critic/issues/845 - - evalOrder - - ifElseChain - - octalLiteral - - regexpSimplify - - sloppyReassign - - truncateCmp - - typeDefFirst - - unnamedResult - - unnecessaryDefer - - whyNoLint - - wrapperFunc - - rangeValCopy - - hugeParam - unused: - go: "1.20" - wrapcheck: - ignoreSigs: - - status.Error( - - .Errorf( - - errors.New( - - errors.Unwrap( - - .Wrap( - - .Wrapf( - - .WithMessage( - - .WithMessagef( - - .WithStack( - - .Complete( + - experimental + - opinionated revive: enable-all-rules: true rules: @@ -145,11 +109,12 @@ linters-settings: # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#add-constant - name: add-constant severity: warning - disabled: false + disabled: true arguments: - maxLitCount: "3" allowStrs: '""' allowInts: "0,1,2,3,42,100" + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#argument-limit - name: argument-limit severity: warning @@ -242,113 +207,85 @@ linters-settings: # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#comment-spacings - name: comment-spacings disabled: true - # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#add-constant - - name: add-constant - disabled: true # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#use-any - name: use-any disabled: true # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#deep-exit - name: deep-exit disabled: true - # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#struct-tag - - name: struct-tag - disabled: true - # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#receiver-naming - - name: receiver-naming - disabled: true # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#receiver-naming - name: nested-structs disabled: true + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#struct-tag + - name: struct-tag + disabled: true + unused: + go: "1.21" + usestdlibvars: + # Suggest the use of http.MethodXX. + # Default: true + http-method: true + # Suggest the use of http.StatusXX. + # Default: true + http-status-code: true + # Suggest the use of time.Weekday.String(). + # Default: true + time-weekday: true + # Suggest the use of time.Month.String(). + # Default: false + time-month: true + # Suggest the use of time.Layout. + # Default: false + time-layout: true + # Suggest the use of crypto.Hash.String(). + # Default: false + crypto-hash: true + # Suggest the use of rpc.DefaultXXPath. + # Default: false + default-rpc-path: true + # Suggest the use of os.DevNull. + # Default: false + os-dev-null: true + # Suggest the use of sql.LevelXX.String(). + # Default: false + sql-isolation-level: true + # Suggest the use of tls.SignatureScheme.String(). + # Default: false + tls-signature-scheme: true + # Suggest the use of constant.Kind.String(). + # Default: false + constant-kind: true + # Suggest the use of syslog.Priority. + # Default: false + syslog-priority: true + wrapcheck: + ignoreSigs: + - status.Error( + - .Errorf( + - errors.New( + - errors.Unwrap( + - .Wrap( + - .Wrapf( + - .WithMessage( + - .WithMessagef( + - .WithStack( + - .Complete( issues: max-same-issues: 0 max-issues-per-linter: 0 - # We are disabling default golangci exclusions because we want to help reviewers to focus on reviewing the most relevant + # We are disabling default golangci exclusions + # because we want to help reviewers to focus on reviewing the most relevant # changes in PRs and avoid nitpicking. exclude-use-default: false exclude-rules: - linters: - wrapcheck path: _test\.go - - linters: - - revive - text: "exported: exported method .*\\.(Reconcile|SetupWithManager|SetupWebhookWithManager) should have comment or be unexported" - - linters: - - errcheck - text: Error return value of .((os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*print(f|ln)?|os\.(Un)?Setenv). is not checked - # Exclude some packages or code to require comments, for example test code, or fake clients. - - linters: - - revive - text: exported (method|function|type|const) (.+) should have comment or be unexported - source: (func|type).*Fake.* - - linters: - - revive - text: exported (method|function|type|const) (.+) should have comment or be unexported - path: fake_\.go - - linters: - - revive - text: exported (method|function|type|const) (.+) should have comment or be unexported - path: "(framework|e2e)/.*.go" - # Disable unparam "always receives" which might not be really - # useful when building libraries. - - linters: - - unparam - text: always receives - # Dot imports for gomega or ginkgo are allowed - # within test files. - - path: _test\.go - text: should not use dot imports - - path: (framework|e2e)/.*.go - text: should not use dot imports - - path: _test\.go - text: cyclomatic complexity - # Append should be able to assign to a different var/slice. - - linters: - - gocritic - text: "appendAssign: append result not assigned to the same slice" - # Disable linters for conversion - - linters: - - staticcheck - text: "SA1019: in.(.+) is deprecated" - path: .*(api|types)\/.*\/conversion.*\.go$ - - linters: - - revive - text: exported (method|function|type|const) (.+) should have comment or be unexported - path: .*(api|types|test)\/.*\/conversion.*\.go$ - - linters: - - revive - text: "var-naming: don't use underscores in Go names;" - path: .*(api|types|test)\/.*\/conversion.*\.go$ - - linters: - - revive - text: "receiver-naming: receiver name" - path: .*(api|types)\/.*\/conversion.*\.go$ - - linters: - - stylecheck - text: "ST1003: should not use underscores in Go names;" - path: .*(api|types|test)\/.*\/conversion.*\.go$ - - linters: - - stylecheck - text: "ST1016: methods on the same type should have the same receiver name" - path: .*(api|types)\/.*\/conversion.*\.go$ - # hack/tools - - linters: - - typecheck - text: import (".+") is a program, not an importable package - path: ^tools\.go$ - # We don't care about defer in for loops in test files. - - linters: - - gocritic - text: "deferInLoop: Possible resource leak, 'defer' is called in the 'for' loop" - path: _test\.go run: timeout: 10m - skip-files: - - "zz_generated.*\\.go$" - - ".*conversion.*\\.go$" - - "test/e2e/*\\.go$" - skip-dirs: - - "third_party" - - "tilt_modules" + go: "1.21" allow-parallel-runners: true - modules-download-mode: readonly + modules-download-mode: vendor + skip-dirs: + - vendor$ + - test/vendor$ diff --git a/config/default/kustomization.yaml b/config/default/kustomization.yaml index c51cf4510..835ccdd63 100644 --- a/config/default/kustomization.yaml +++ b/config/default/kustomization.yaml @@ -6,18 +6,14 @@ commonLabels: cluster.x-k8s.io/provider: "infrastructure-cluster-stack-operator" resources: -- ../crd -- ../rbac -- ../manager -#- ../webhook -- ../certmanager + - ../crd + - ../rbac + - ../manager + - ../certmanager patchesStrategicMerge: -- manager_config_patch.yaml -#- manager_webhook_patch.yaml -#- webhookcainjection_patch.yaml -- manager_pull_policy.yaml - + - manager_config_patch.yaml + - manager_pull_policy.yaml # vars: # - name: CERTIFICATE_NAMESPACE # namespace of the certificate CR # objref: @@ -44,4 +40,4 @@ patchesStrategicMerge: # objref: # kind: Service # version: v1 -# name: webhook-service \ No newline at end of file +# name: webhook-service diff --git a/internal/clusterstackrelease/util.go b/internal/clusterstackrelease/util.go index 27c1bcfbb..a128d4da3 100644 --- a/internal/clusterstackrelease/util.go +++ b/internal/clusterstackrelease/util.go @@ -48,11 +48,12 @@ func Summary(csr *csov1alpha1.ClusterStackRelease) (csov1alpha1.ClusterStackRele // if provider-specific work is done, we are left with applying objects // We don't expect the condition to be not set at all, hence no else case here - if conditions.IsTrue(csr, csov1alpha1.ProviderClusterStackReleaseReadyCondition) { + switch { + case conditions.IsTrue(csr, csov1alpha1.ProviderClusterStackReleaseReadyCondition): summary.Phase = csov1alpha1.ClusterStackReleasePhaseApplyingObjects - } else if conditions.IsTrue(csr, csov1alpha1.ClusterStackReleaseAssetsReadyCondition) { + case conditions.IsTrue(csr, csov1alpha1.ClusterStackReleaseAssetsReadyCondition): summary.Phase = csov1alpha1.ClusterStackReleasePhaseProviderSpecificWork - } else if conditions.IsFalse(csr, csov1alpha1.ClusterStackReleaseAssetsReadyCondition) { + case conditions.IsFalse(csr, csov1alpha1.ClusterStackReleaseAssetsReadyCondition): summary.Phase = csov1alpha1.ClusterStackReleasePhaseDownloadingAssets } diff --git a/internal/controller/clusterstack_controller.go b/internal/controller/clusterstack_controller.go index 310adec1c..b58153280 100644 --- a/internal/controller/clusterstack_controller.go +++ b/internal/controller/clusterstack_controller.go @@ -194,7 +194,7 @@ func (r *ClusterStackReconciler) Reconcile(ctx context.Context, req reconcile.Re } } - if err := r.getOrCreateClusterStackRelease(ctx, csr.Name, req.Namespace, *ownerRef, providerRef); err != nil { + if err := r.getOrCreateClusterStackRelease(ctx, csr.Name, req.Namespace, ownerRef, providerRef); err != nil { conditions.MarkFalse(clusterStack, csov1alpha1.ClusterStackReleasesSyncedCondition, csov1alpha1.FailedToCreateOrUpdateReason, @@ -228,7 +228,7 @@ func (r *ClusterStackReconciler) Reconcile(ctx context.Context, req reconcile.Re return reconcile.Result{}, nil } -func (r *ClusterStackReconciler) getOrCreateClusterStackRelease(ctx context.Context, name, namespace string, ownerRef metav1.OwnerReference, providerRef *corev1.ObjectReference) error { +func (r *ClusterStackReconciler) getOrCreateClusterStackRelease(ctx context.Context, name, namespace string, ownerRef *metav1.OwnerReference, providerRef *corev1.ObjectReference) error { clusterStackRelease := &csov1alpha1.ClusterStackRelease{} err := r.Get(ctx, types.NamespacedName{Name: name, Namespace: namespace}, clusterStackRelease) @@ -250,7 +250,7 @@ func (r *ClusterStackReconciler) getOrCreateClusterStackRelease(ctx context.Cont Kind: "ClusterStackRelease", APIVersion: "clusterstack.x-k8s.io/v1alpha1", } - clusterStackRelease.SetOwnerReferences([]metav1.OwnerReference{ownerRef}) + clusterStackRelease.SetOwnerReferences([]metav1.OwnerReference{*ownerRef}) clusterStackRelease.Spec.ProviderRef = providerRef if err := r.Create(ctx, clusterStackRelease); err != nil { @@ -431,9 +431,11 @@ func (r *ClusterStackReconciler) getExistingClusterStackReleases(ctx context.Con existingClusterStackReleases := make([]*csov1alpha1.ClusterStackRelease, 0, len(csrList.Items)) - for i, csr := range csrList.Items { - for _, ownerRef := range csr.GetOwnerReferences() { - if matchesOwnerRef(ownerRef, clusterStack) { + for i := range csrList.Items { + csr := csrList.Items[i] + for i := range csr.GetOwnerReferences() { + ownerRef := csr.GetOwnerReferences()[i] + if matchesOwnerRef(&ownerRef, clusterStack) { existingClusterStackReleases = append(existingClusterStackReleases, &csrList.Items[i]) break } @@ -499,7 +501,7 @@ func makeDiff(clusterStackReleases []*csov1alpha1.ClusterStackRelease, latest, l // getLatestReadyClusterStackRelease returns the latest ready clusterStackRelease or nil. // If one has been found, it returns additionally the Kubernetes version found in the objects's status. -func getLatestReadyClusterStackRelease(clusterStackReleases []*csov1alpha1.ClusterStackRelease) (*string, string, error) { +func getLatestReadyClusterStackRelease(clusterStackReleases []*csov1alpha1.ClusterStackRelease) (latest *string, k8sversion string, err error) { clusterStackObjects := make(clusterstack.ClusterStacks, 0, len(clusterStackReleases)) mapKubernetesVersions := make(map[string]string) @@ -525,8 +527,10 @@ func getLatestReadyClusterStackRelease(clusterStackReleases []*csov1alpha1.Clust sort.Sort((clusterStackObjects)) // return the latest one - latest := clusterStackObjects[len(clusterStackObjects)-1].String() - return &latest, mapKubernetesVersions[latest], nil + cs := clusterStackObjects[len(clusterStackObjects)-1].String() + latest = &cs + k8sversion = mapKubernetesVersions[*latest] + return latest, k8sversion, nil } func getLatestReleaseFromRemoteRepository(ctx context.Context, clusterStack *csov1alpha1.ClusterStack, gc githubclient.Client) (*string, error) { @@ -546,7 +550,7 @@ func getLatestReleaseFromRemoteRepository(ctx context.Context, clusterStack *cso var clusterStacks clusterstack.ClusterStacks for _, ghRelease := range ghReleases { - clusterStackObject, matches, err := matchesSpec(ghRelease.GetTagName(), clusterStack.Spec) + clusterStackObject, matches, err := matchesSpec(ghRelease.GetTagName(), &clusterStack.Spec) if err != nil { return nil, fmt.Errorf("failed to get match release tag %q with spec of ClusterStack: %w", ghRelease.GetTagName(), err) } @@ -610,7 +614,7 @@ func getUsableClusterStackReleaseVersions(clusterStackReleases []*csov1alpha1.Cl return usableVersions, nil } -func matchesOwnerRef(a metav1.OwnerReference, clusterStack *csov1alpha1.ClusterStack) bool { +func matchesOwnerRef(a *metav1.OwnerReference, clusterStack *csov1alpha1.ClusterStack) bool { aGV, err := schema.ParseGroupVersion(a.APIVersion) if err != nil { return false @@ -619,7 +623,7 @@ func matchesOwnerRef(a metav1.OwnerReference, clusterStack *csov1alpha1.ClusterS return aGV.Group == clusterStack.GroupVersionKind().Group && a.Kind == clusterStack.Kind && a.Name == clusterStack.Name } -func matchesSpec(str string, spec csov1alpha1.ClusterStackSpec) (clusterstack.ClusterStack, bool, error) { +func matchesSpec(str string, spec *csov1alpha1.ClusterStackSpec) (clusterstack.ClusterStack, bool, error) { csObject, err := clusterstack.NewFromString(str) if err != nil { return clusterstack.ClusterStack{}, false, fmt.Errorf("failed to get clusterstack object from string %q: %w", str, err) @@ -631,20 +635,20 @@ func matchesSpec(str string, spec csov1alpha1.ClusterStackSpec) (clusterstack.Cl csObject.Provider == spec.Provider, nil } -func unstructuredSpecEqual(oldObj, newObj map[string]interface{}) (map[string]interface{}, bool, error) { - oldSpec, found, err := unstructured.NestedMap(oldObj, "spec") +func unstructuredSpecEqual(oldObj, newObj map[string]interface{}) (newSpec map[string]interface{}, isEqual bool, err error) { + oldSpec, isEqual, err := unstructured.NestedMap(oldObj, "spec") if err != nil { return nil, false, fmt.Errorf("failed to retrieve spec map of object: %w", err) } - if !found { + if !isEqual { return nil, false, fmt.Errorf("missing spec") } - newSpec, found, err := unstructured.NestedMap(newObj, "spec") + newSpec, isEqual, err = unstructured.NestedMap(newObj, "spec") if err != nil { return nil, false, fmt.Errorf("failed to retrieve spec map of object: %w", err) } - if !found { + if !isEqual { return nil, false, fmt.Errorf("missing spec") } @@ -705,7 +709,7 @@ func (r *ClusterStackReconciler) SetupWithManager(ctx context.Context, mgr ctrl. // ClusterStackReleaseToClusterStack is a handler.ToRequestsFunc to be used to enqueue requests for reconciliation // for ClusterStacks that might get updated by changes in ClusterStackReleases. -func (_ *ClusterStackReconciler) ClusterStackReleaseToClusterStack(ctx context.Context) handler.MapFunc { +func (*ClusterStackReconciler) ClusterStackReleaseToClusterStack(ctx context.Context) handler.MapFunc { logger := log.FromContext(ctx) return func(ctx context.Context, o client.Object) []reconcile.Request { result := []reconcile.Request{} diff --git a/internal/controller/clusterstackrelease_controller.go b/internal/controller/clusterstackrelease_controller.go index 1c3884eb1..4c26f6216 100644 --- a/internal/controller/clusterstackrelease_controller.go +++ b/internal/controller/clusterstackrelease_controller.go @@ -142,7 +142,7 @@ func (r *ClusterStackReleaseReconciler) Reconcile(ctx context.Context, req recon kubeClient := r.KubeClientFactory.NewClient(req.Namespace, r.RESTConfig) if !clusterStackRelease.DeletionTimestamp.IsZero() { - return r.reconcileDelete(ctx, releaseAssets, req.Namespace, kubeClient, clusterStackRelease) + return r.reconcileDelete(ctx, &releaseAssets, req.Namespace, kubeClient, clusterStackRelease) } clusterStackRelease.Status.KubernetesVersion = releaseAssets.Meta.Versions.Kubernetes @@ -166,7 +166,7 @@ func (r *ClusterStackReleaseReconciler) Reconcile(ctx context.Context, req recon return reconcile.Result{}, nil } - shouldRequeue, err := r.templateAndApply(ctx, releaseAssets, clusterStackRelease, kubeClient) + shouldRequeue, err := r.templateAndApply(ctx, &releaseAssets, clusterStackRelease, kubeClient) if err != nil { return reconcile.Result{}, fmt.Errorf("failed to template and apply: %w", err) } @@ -183,7 +183,7 @@ func (r *ClusterStackReleaseReconciler) Reconcile(ctx context.Context, req recon } // reconcileDelete controls the deletion of clusterstackrelease objects. -func (r *ClusterStackReleaseReconciler) reconcileDelete(ctx context.Context, releaseAssets release.Release, namespace string, kubeClient kube.Client, clusterStackReleaseCR *csov1alpha1.ClusterStackRelease) (reconcile.Result, error) { +func (r *ClusterStackReleaseReconciler) reconcileDelete(ctx context.Context, releaseAssets *release.Release, namespace string, kubeClient kube.Client, clusterStackReleaseCR *csov1alpha1.ClusterStackRelease) (reconcile.Result, error) { presentClusterClasses, err := getUsedClusterClasses(ctx, r.Client, namespace) if err != nil { return reconcile.Result{}, fmt.Errorf("failed to get usedClusterClass: %w", err) @@ -272,7 +272,7 @@ func (r *ClusterStackReleaseReconciler) updateProviderClusterStackRelease(ctx co return ready, nil } -func (r *ClusterStackReleaseReconciler) templateAndApply(ctx context.Context, releaseAssets release.Release, clusterStackRelease *csov1alpha1.ClusterStackRelease, kubeClient kube.Client) (bool, error) { +func (r *ClusterStackReleaseReconciler) templateAndApply(ctx context.Context, releaseAssets *release.Release, clusterStackRelease *csov1alpha1.ClusterStackRelease, kubeClient kube.Client) (bool, error) { // template helm chart and apply objects template, err := r.templateClusterClassHelmChart(releaseAssets, clusterStackRelease.Name, clusterStackRelease.Namespace) if err != nil { @@ -295,7 +295,7 @@ func (r *ClusterStackReleaseReconciler) templateAndApply(ctx context.Context, re } // templateClusterClassHelmChart templates the clusterClass helm chart. -func (_ *ClusterStackReleaseReconciler) templateClusterClassHelmChart(releaseAssets release.Release, name, namespace string) ([]byte, error) { +func (*ClusterStackReleaseReconciler) templateClusterClassHelmChart(releaseAssets *release.Release, name, namespace string) ([]byte, error) { clusterClassChart, err := releaseAssets.ClusterClassChartPath() if err != nil { return nil, fmt.Errorf("failed to get clusterClass helm chart path: %w", err) diff --git a/internal/controller/utils.go b/internal/controller/utils.go index 87e6971a8..186946ef5 100644 --- a/internal/controller/utils.go +++ b/internal/controller/utils.go @@ -33,7 +33,8 @@ func getUsedClusterClasses(ctx context.Context, c client.Client, namespace strin usedClusterClasses := make([]string, 0, len(clusterList.Items)) // list the names of all ClusterClasses that are referenced in Cluster objects - for _, cluster := range clusterList.Items { + for i := range clusterList.Items { + cluster := clusterList.Items[i] if cluster.Spec.Topology == nil { continue } diff --git a/pkg/clusterstack/clusterstack.go b/pkg/clusterstack/clusterstack.go index 62c9cdf74..381458034 100644 --- a/pkg/clusterstack/clusterstack.go +++ b/pkg/clusterstack/clusterstack.go @@ -128,7 +128,7 @@ func New(provider, name, kubernetesVersion, csVersion string) (ClusterStack, err } // Validate validates a given ClusterStack. -func (cs ClusterStack) Validate() error { +func (cs *ClusterStack) Validate() error { if cs.Provider == "" { return ErrInvalidProvider } @@ -147,7 +147,7 @@ func (cs ClusterStack) Validate() error { return nil } -func (cs ClusterStack) String() string { +func (cs *ClusterStack) String() string { // release tag: myprovider-myclusterstack-1-26-v1 return strings.Join([]string{cs.Provider, cs.Name, cs.KubernetesVersion.String(), cs.Version.String()}, Separator) } diff --git a/pkg/kube/kube.go b/pkg/kube/kube.go index 96a690626..c69d8b827 100644 --- a/pkg/kube/kube.go +++ b/pkg/kube/kube.go @@ -57,7 +57,7 @@ func NewFactory() Factory { var _ Client = &kube{} -func (_ *factory) NewClient(namespace string, resCfg *rest.Config) Client { +func (*factory) NewClient(namespace string, resCfg *rest.Config) Client { return &kube{ Namespace: namespace, RestConfig: resCfg, diff --git a/pkg/version/version.go b/pkg/version/version.go index 08d0aebd0..69175d105 100644 --- a/pkg/version/version.go +++ b/pkg/version/version.go @@ -45,7 +45,7 @@ func New(version string) (Version, error) { var err error channel := ChannelStable - re := regexp.MustCompile(`^v\d+(-\b\w+\b[-]\d+)?$`) + re := regexp.MustCompile(`^v\d+(-\b\w+\b-\d+)?$`) match := re.FindStringSubmatch(version) if len(match) == 0 { @@ -118,19 +118,23 @@ func (csv Version) Compare(input Version) (int, error) { if csv.Channel != input.Channel { return 0, fmt.Errorf("cannot compare versions with different channels %s and %s", csv.Channel, input.Channel) } - if csv.Major > input.Major { + + switch { + case csv.Major > input.Major: return 1, nil - } else if csv.Major < input.Major { + case csv.Major < input.Major: return -1, nil - } else if csv.Major == input.Major { - if csv.Patch > input.Patch { + case csv.Major == input.Major: + switch { + case csv.Patch > input.Patch: return 1, nil - } else if csv.Patch < input.Patch { + case csv.Patch < input.Patch: return -1, nil - } else if csv.Patch == input.Patch { + case csv.Patch == input.Patch: return 0, nil } } + return 0, nil }