From bb6a7b8f07a9b5e5e59aa99f64a3616d366a2d1d Mon Sep 17 00:00:00 2001 From: Hidde Beydals Date: Mon, 13 Feb 2023 14:01:25 +0100 Subject: [PATCH] Support shortening of revision with digest The `\b` in the regular expression ensures we only match with a hexadecimal notation as awhole, while still allowing to match with e.g. `sha1:...` which would not have been possible by using `\W` as this includes `_`. Signed-off-by: Hidde Beydals --- cmd/flux/get_kustomization.go | 20 +--- cmd/flux/get_source_bucket.go | 4 + cmd/flux/get_source_chart.go | 5 + cmd/flux/get_source_git.go | 9 +- cmd/flux/get_source_helm.go | 4 + cmd/flux/get_source_oci.go | 4 + .../get_kustomization_from_git.golden | 4 +- cmd/flux/testdata/oci/get_oci.golden | 4 +- internal/utils/hex.go | 39 +++++++ internal/utils/hex_test.go | 104 ++++++++++++++++++ 10 files changed, 172 insertions(+), 25 deletions(-) create mode 100644 internal/utils/hex.go create mode 100644 internal/utils/hex_test.go diff --git a/cmd/flux/get_kustomization.go b/cmd/flux/get_kustomization.go index 5d20f639..0dc9f72d 100644 --- a/cmd/flux/get_kustomization.go +++ b/cmd/flux/get_kustomization.go @@ -18,15 +18,15 @@ package main import ( "fmt" - "regexp" "strconv" "strings" "github.com/spf13/cobra" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2" + + "github.com/fluxcd/flux2/internal/utils" ) var getKsCmd = &cobra.Command{ @@ -80,10 +80,8 @@ func (a kustomizationListAdapter) summariseItem(i int, includeNamespace bool, in item := a.Items[i] revision := item.Status.LastAppliedRevision status, msg := statusAndMessage(item.Status.Conditions) - if status == string(metav1.ConditionTrue) { - revision = shortenCommitSha(revision) - msg = shortenCommitSha(msg) - } + revision = utils.TruncateHex(revision) + msg = utils.TruncateHex(msg) return append(nameColumns(&item, includeNamespace, includeKind), revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg) } @@ -100,13 +98,3 @@ func (a kustomizationListAdapter) statusSelectorMatches(i int, conditionType, co item := a.Items[i] return statusMatches(conditionType, conditionStatus, item.Status.Conditions) } - -func shortenCommitSha(msg string) string { - r := regexp.MustCompile("/([a-f0-9]{40})$") - sha := r.FindString(msg) - if sha != "" { - msg = strings.Replace(msg, sha, string([]rune(sha)[:8]), -1) - } - - return msg -} diff --git a/cmd/flux/get_source_bucket.go b/cmd/flux/get_source_bucket.go index 4eed78e1..b1b283c4 100644 --- a/cmd/flux/get_source_bucket.go +++ b/cmd/flux/get_source_bucket.go @@ -25,6 +25,8 @@ import ( "k8s.io/apimachinery/pkg/runtime" sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" + + "github.com/fluxcd/flux2/internal/utils" ) var getSourceBucketCmd = &cobra.Command{ @@ -80,6 +82,8 @@ func (a *bucketListAdapter) summariseItem(i int, includeNamespace bool, includeK revision = item.GetArtifact().Revision } status, msg := statusAndMessage(item.Status.Conditions) + revision = utils.TruncateHex(revision) + msg = utils.TruncateHex(msg) return append(nameColumns(&item, includeNamespace, includeKind), revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg) } diff --git a/cmd/flux/get_source_chart.go b/cmd/flux/get_source_chart.go index 6f5ccecd..bb275f60 100644 --- a/cmd/flux/get_source_chart.go +++ b/cmd/flux/get_source_chart.go @@ -25,6 +25,8 @@ import ( "k8s.io/apimachinery/pkg/runtime" sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" + + "github.com/fluxcd/flux2/internal/utils" ) var getSourceHelmChartCmd = &cobra.Command{ @@ -80,6 +82,9 @@ func (a *helmChartListAdapter) summariseItem(i int, includeNamespace bool, inclu revision = item.GetArtifact().Revision } status, msg := statusAndMessage(item.Status.Conditions) + // NB: do not shorten revision as it contains a SemVer + // Message may still contain reference of e.g. commit chart was build from + msg = utils.TruncateHex(msg) return append(nameColumns(&item, includeNamespace, includeKind), revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg) } diff --git a/cmd/flux/get_source_git.go b/cmd/flux/get_source_git.go index 4b4f7c43..5df1b907 100644 --- a/cmd/flux/get_source_git.go +++ b/cmd/flux/get_source_git.go @@ -22,10 +22,11 @@ import ( "strings" "github.com/spf13/cobra" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" + + "github.com/fluxcd/flux2/internal/utils" ) var getSourceGitCmd = &cobra.Command{ @@ -81,10 +82,8 @@ func (a *gitRepositoryListAdapter) summariseItem(i int, includeNamespace bool, i revision = item.GetArtifact().Revision } status, msg := statusAndMessage(item.Status.Conditions) - if status == string(metav1.ConditionTrue) { - revision = shortenCommitSha(revision) - msg = shortenCommitSha(msg) - } + revision = utils.TruncateHex(revision) + msg = utils.TruncateHex(msg) return append(nameColumns(&item, includeNamespace, includeKind), revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg) } diff --git a/cmd/flux/get_source_helm.go b/cmd/flux/get_source_helm.go index 7b4eb8e8..2ac0f0f9 100644 --- a/cmd/flux/get_source_helm.go +++ b/cmd/flux/get_source_helm.go @@ -25,6 +25,8 @@ import ( "k8s.io/apimachinery/pkg/runtime" sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" + + "github.com/fluxcd/flux2/internal/utils" ) var getSourceHelmCmd = &cobra.Command{ @@ -80,6 +82,8 @@ func (a *helmRepositoryListAdapter) summariseItem(i int, includeNamespace bool, revision = item.GetArtifact().Revision } status, msg := statusAndMessage(item.Status.Conditions) + revision = utils.TruncateHex(revision) + msg = utils.TruncateHex(msg) return append(nameColumns(&item, includeNamespace, includeKind), revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg) } diff --git a/cmd/flux/get_source_oci.go b/cmd/flux/get_source_oci.go index b79f73f8..aac2a60e 100644 --- a/cmd/flux/get_source_oci.go +++ b/cmd/flux/get_source_oci.go @@ -25,6 +25,8 @@ import ( "k8s.io/apimachinery/pkg/runtime" sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" + + "github.com/fluxcd/flux2/internal/utils" ) var getSourceOCIRepositoryCmd = &cobra.Command{ @@ -80,6 +82,8 @@ func (a *ociRepositoryListAdapter) summariseItem(i int, includeNamespace bool, i revision = item.GetArtifact().Revision } status, msg := statusAndMessage(item.Status.Conditions) + revision = utils.TruncateHex(revision) + msg = utils.TruncateHex(msg) return append(nameColumns(&item, includeNamespace, includeKind), revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg) } diff --git a/cmd/flux/testdata/kustomization/get_kustomization_from_git.golden b/cmd/flux/testdata/kustomization/get_kustomization_from_git.golden index b4f7e31c..8aa32cc6 100644 --- a/cmd/flux/testdata/kustomization/get_kustomization_from_git.golden +++ b/cmd/flux/testdata/kustomization/get_kustomization_from_git.golden @@ -1,2 +1,2 @@ -NAME REVISION SUSPENDED READY MESSAGE -tkfg 6.0.0/627d5c4 False True Applied revision: 6.0.0/627d5c4 +NAME REVISION SUSPENDED READY MESSAGE +tkfg 6.0.0/627d5c4b False True Applied revision: 6.0.0/627d5c4b diff --git a/cmd/flux/testdata/oci/get_oci.golden b/cmd/flux/testdata/oci/get_oci.golden index 5397cae2..153ebd0d 100644 --- a/cmd/flux/testdata/oci/get_oci.golden +++ b/cmd/flux/testdata/oci/get_oci.golden @@ -1,2 +1,2 @@ -NAME REVISION SUSPENDED READY MESSAGE -thrfg 6.1.6/dbdb109711ffb3be77504d2670dbe13c24dd63d8d7f1fb489d350e5bfe930dd3 False True stored artifact for digest '6.1.6/dbdb109711ffb3be77504d2670dbe13c24dd63d8d7f1fb489d350e5bfe930dd3' +NAME REVISION SUSPENDED READY MESSAGE +thrfg 6.1.6/dbdb1097 False True stored artifact for digest '6.1.6/dbdb1097' diff --git a/internal/utils/hex.go b/internal/utils/hex.go new file mode 100644 index 00000000..310e7432 --- /dev/null +++ b/internal/utils/hex.go @@ -0,0 +1,39 @@ +/* +Copyright 2023 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package utils + +import ( + "regexp" + "strings" +) + +// hexRegexp matches any hexadecimal notation between 40 and 128 characters. +var hexRegexp = regexp.MustCompile(`\b[a-f0-9]{40,128}\b`) + +// TruncateHex will replace any hexadecimal notation between 40 and 128 +// characters (SHA-1 up to SHA-512) within the given string with a truncated +// version of 8 characters. +func TruncateHex(str string) string { + if str == "" { + return "" + } + hits := hexRegexp.FindAllString(str, -1) + for _, v := range hits { + str = strings.Replace(str, v, string([]rune(v)[:8]), -1) + } + return str +} diff --git a/internal/utils/hex_test.go b/internal/utils/hex_test.go new file mode 100644 index 00000000..5cbc9d3c --- /dev/null +++ b/internal/utils/hex_test.go @@ -0,0 +1,104 @@ +/* +Copyright 2023 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package utils + +import ( + "testing" + + . "github.com/onsi/gomega" +) + +func TestTruncateHex(t *testing.T) { + tests := []struct { + name string + str string + want string + }{ + { + name: "SHA1 hash", + str: "16cfcc0b9066b3234dda29927ac1c19860d9663f", + want: "16cfcc0b", + }, + { + name: "SHA256 hash", + str: "c2448c95e262b10f9c1137bf1472f51c04dffca76966ff15eff409d0b300c0b0", + want: "c2448c95", + }, + { + name: "BLAKE3 hash", + str: "d7b332559f4e57c01dcbe24e53346b4e47696fd2a07f39639a4017a8c3a1d045", + want: "d7b33255", + }, + { + name: "SHA512 hash", + str: "dd81564b7e1e1d5986b166c21963d602f47f8610bf2a6ebbfd2f9c1e5ef05ef134f07e587383cbc049325c43e0e6817b5a282a74c0d569a5e057118484989781", + want: "dd81564b", + }, + { + name: "part of digest", + str: "sha256:c2448c95e262b10f9c1137bf1472f51c04dffca76966ff15eff409d0b300c0b0", + want: "sha256:c2448c95", + }, + { + name: "part of revision with digest", + str: "tag@sha256:c2448c95e262b10f9c1137bf1472f51c04dffca76966ff15eff409d0b300c0b0", + want: "tag@sha256:c2448c95", + }, + { + name: "legacy revision with hash", + str: "HEAD/16cfcc0b9066b3234dda29927ac1c19860d9663f", + want: "HEAD/16cfcc0b", + }, + { + name: "hex exceeding max length", + str: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + want: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + }, + { + name: "hex under min length", + str: "ffffffffffffff", + want: "ffffffffffffff", + }, + { + name: "within string", + str: "this is a lengthy string with a hash 16cfcc0b9066b3234dda29927ac1c19860d9663f in it", + want: "this is a lengthy string with a hash 16cfcc0b in it", + }, + { + name: "within string (quoted)", + str: "this is a lengthy string with a hash \"c2448c95e262b10f9c1137bf1472f51c04dffca76966ff15eff409d0b300c0b0\" in it", + want: "this is a lengthy string with a hash \"c2448c95\" in it", + }, + { + name: "within string (single quoted)", + str: "this is a lengthy string with a hash 'sha256:c2448c95e262b10f9c1137bf1472f51c04dffca76966ff15eff409d0b300c0b0' in it", + want: "this is a lengthy string with a hash 'sha256:c2448c95' in it", + }, + { + name: "arbitrary string", + str: "which should not be modified", + want: "which should not be modified", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + + g.Expect(TruncateHex(tt.str)).To(Equal(tt.want)) + }) + } +}