Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions pkg/certs/cert-inspection/certgraphanalysis/metadata_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ import (
"fmt"
"os"
"regexp"
"strconv"
"strings"
"time"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/openshift/library-go/pkg/certs/cert-inspection/certgraphapi"
"github.com/openshift/library-go/pkg/operator/certrotation"
corev1 "k8s.io/api/core/v1"
)

Expand Down Expand Up @@ -158,8 +161,32 @@ var (
secret.Name = strings.ReplaceAll(secret.Name, hash, "<hash>")
},
}
RewriteRefreshPeriod = &metadataOptions{
rewriteSecretFn: func(secret *corev1.Secret) {
humanizeRefreshPeriodFromMetadata(secret.Annotations)
},
rewriteConfigMapFn: func(configMap *corev1.ConfigMap) {
humanizeRefreshPeriodFromMetadata(configMap.Annotations)
},
}
)

func humanizeRefreshPeriodFromMetadata(annotations map[string]string) {
period, ok := annotations[certrotation.CertificateRefreshPeriodAnnotation]
if !ok {
return
}
d, err := time.ParseDuration(period)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to parse certificate refresh period %q: %v\n", period, err)
return
}
humanReadableDate := durationToHumanReadableString(d)
annotations[certrotation.CertificateRefreshPeriodAnnotation] = humanReadableDate
annotations[rewritePrefix+"RewriteRefreshPeriod"] = period
return
}

// skipRevisionedInOnDiskLocation returns true if location is for revisioned certificate and needs to be skipped
func skipRevisionedInOnDiskLocation(location certgraphapi.OnDiskLocation) bool {
if len(location.Path) == 0 {
Expand Down Expand Up @@ -235,3 +262,62 @@ func StripRootFSMountPoint(rootfsMount string) *metadataOptions {
},
}
}

// durationToHumanReadableString formats a duration into a human-readable string.
// Unlike Go's built-in `time.Duration.String()`, which returns a string like "72h0m0s", this function returns a more concise format like "3d" or "5d4h25m".
// Implementation is based on https://github.com/gomodules/sprig/blob/master/date.go#L97-L139,
// but it doesn't round the duration to the nearest largest value but converts it precisely
// This function rounds duration to the nearest second and handles negative durations by taking the absolute value.
func durationToHumanReadableString(d time.Duration) string {
if d == 0 {
return "0s"
}
// Handle negative durations by taking the absolute value
// This also rounds the duration to the nearest second
u := uint64(d.Abs().Seconds())

var b strings.Builder

writeUnit := func(value uint64, suffix string) {
if value > 0 {
b.WriteString(strconv.FormatUint(value, 10))
b.WriteString(suffix)
}
}

const (
// Unit values in seconds
year = 60 * 60 * 24 * 365
month = 60 * 60 * 24 * 30
day = 60 * 60 * 24
hour = 60 * 60
minute = 60
second = 1
)

years := u / year
u %= year
writeUnit(years, "y")

months := u / month
u %= month
writeUnit(months, "mo")

days := u / day
u %= day
writeUnit(days, "d")

hours := u / hour
u %= hour
writeUnit(hours, "h")

minutes := u / minute
u %= minute
writeUnit(minutes, "m")

seconds := u / second
u %= second
writeUnit(seconds, "s")

return b.String()
}
79 changes: 79 additions & 0 deletions pkg/certs/cert-inspection/certgraphanalysis/metadata_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package certgraphanalysis

import (
"testing"
"time"

"github.com/google/go-cmp/cmp"
)

func TestDurationToHumanReadableString(t *testing.T) {
tests := []struct {
duration time.Duration
expected string
}{
{0, "0s"},
{time.Second, "1s"},
{2 * time.Second, "2s"},
{time.Minute, "1m"},
{time.Minute + 30*time.Second, "1m30s"},
{time.Hour, "1h"},
{25 * time.Hour, "1d1h"},
{30 * 24 * time.Hour, "1mo"},
{365 * 24 * time.Hour, "1y"},
{400 * 24 * time.Hour, "1y1mo5d"},
{-time.Minute, "1m"}, // negative duration
{-400 * 24 * time.Hour, "1y1mo5d"}, // negative composite
{3*time.Minute + 4*time.Second, "3m4s"},
}
for _, test := range tests {
t.Run(test.expected, func(t *testing.T) {
result := durationToHumanReadableString(test.duration)
if result != test.expected {
t.Errorf("expected %s, got %s", test.expected, result)
}
})
}
}

func TestHumanizeRefreshPeriodFromMetadata(t *testing.T) {
tests := []struct {
metadata string
expected string
}{
{
metadata: "72h00m00s",
expected: "3d",
},
{
metadata: "124h25m00s",
expected: "5d4h25m",
},
{
metadata: "82080h00m00s",
expected: "9y4mo15d",
},
}
for _, test := range tests {
t.Run(test.metadata, func(t *testing.T) {
result := map[string]string{
"certificates.openshift.io/refresh-period": test.metadata,
}
humanizeRefreshPeriodFromMetadata(result)

expected := map[string]string{
"certificates.openshift.io/refresh-period": test.expected,
"rewritten.cert-info.openshift.io/RewriteRefreshPeriod": test.metadata,
}
diff := cmp.Diff(expected, result)
if diff != "" {
t.Errorf("expected %v, got %v, diff: %s", test.expected, result, diff)
}
})
}
}

func TestHumanizeRefreshPeriodFromMetadataNils(t *testing.T) {
humanizeRefreshPeriodFromMetadata(nil)
humanizeRefreshPeriodFromMetadata(map[string]string{})
}