Skip to content

Commit 507f6ad

Browse files
committed
cert inspection: add an option to make refresh period annotation human-readable
This option converts `time.ParseDuration`-compatible string of duration (i.e. `77h30m00s`) into human-redable string (`3d5h30m`) so that TLS registry markdowns have improved readability
1 parent 03d85c4 commit 507f6ad

File tree

2 files changed

+165
-0
lines changed

2 files changed

+165
-0
lines changed

pkg/certs/cert-inspection/certgraphanalysis/metadata_options.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@ import (
44
"fmt"
55
"os"
66
"regexp"
7+
"strconv"
78
"strings"
9+
"time"
810

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

1113
"github.com/openshift/library-go/pkg/certs/cert-inspection/certgraphapi"
14+
"github.com/openshift/library-go/pkg/operator/certrotation"
1215
corev1 "k8s.io/api/core/v1"
1316
)
1417

@@ -158,8 +161,32 @@ var (
158161
secret.Name = strings.ReplaceAll(secret.Name, hash, "<hash>")
159162
},
160163
}
164+
RewriteRefreshPeriod = &metadataOptions{
165+
rewriteSecretFn: func(secret *corev1.Secret) {
166+
humanizeRefreshPeriodFromMetadata(secret.Annotations)
167+
},
168+
rewriteConfigMapFn: func(configMap *corev1.ConfigMap) {
169+
humanizeRefreshPeriodFromMetadata(configMap.Annotations)
170+
},
171+
}
161172
)
162173

174+
func humanizeRefreshPeriodFromMetadata(annotations map[string]string) {
175+
period, ok := annotations[certrotation.CertificateRefreshPeriodAnnotation]
176+
if !ok {
177+
return
178+
}
179+
d, err := time.ParseDuration(period)
180+
if err != nil {
181+
fmt.Fprintf(os.Stderr, "Failed to parse certificate refresh period %q: %v\n", period, err)
182+
return
183+
}
184+
humanReadableDate := durationToHumanReadableString(d)
185+
annotations[certrotation.CertificateRefreshPeriodAnnotation] = humanReadableDate
186+
annotations[rewritePrefix+"RewriteRefreshPeriod"] = period
187+
return
188+
}
189+
163190
// skipRevisionedInOnDiskLocation returns true if location is for revisioned certificate and needs to be skipped
164191
func skipRevisionedInOnDiskLocation(location certgraphapi.OnDiskLocation) bool {
165192
if len(location.Path) == 0 {
@@ -235,3 +262,62 @@ func StripRootFSMountPoint(rootfsMount string) *metadataOptions {
235262
},
236263
}
237264
}
265+
266+
// durationToHumanReadableString formats a duration into a human-readable string.
267+
// 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".
268+
// Implementation is based on https://github.com/gomodules/sprig/blob/master/date.go#L97-L139,
269+
// but it doesn't round the duration to the nearest largest value but converts it precisely
270+
// This function rounds duration to the nearest second and handles negative durations by taking the absolute value.
271+
func durationToHumanReadableString(d time.Duration) string {
272+
if d == 0 {
273+
return "0s"
274+
}
275+
// Handle negative durations by taking the absolute value
276+
// This also rounds the duration to the nearest second
277+
u := uint64(d.Abs().Seconds())
278+
279+
var b strings.Builder
280+
281+
writeUnit := func(value uint64, suffix string) {
282+
if value > 0 {
283+
b.WriteString(strconv.FormatUint(value, 10))
284+
b.WriteString(suffix)
285+
}
286+
}
287+
288+
const (
289+
// Unit values in seconds
290+
year = 60 * 60 * 24 * 365
291+
month = 60 * 60 * 24 * 30
292+
day = 60 * 60 * 24
293+
hour = 60 * 60
294+
minute = 60
295+
second = 1
296+
)
297+
298+
years := u / year
299+
u %= year
300+
writeUnit(years, "y")
301+
302+
months := u / month
303+
u %= month
304+
writeUnit(months, "mo")
305+
306+
days := u / day
307+
u %= day
308+
writeUnit(days, "d")
309+
310+
hours := u / hour
311+
u %= hour
312+
writeUnit(hours, "h")
313+
314+
minutes := u / minute
315+
u %= minute
316+
writeUnit(minutes, "m")
317+
318+
seconds := u / second
319+
u %= second
320+
writeUnit(seconds, "s")
321+
322+
return b.String()
323+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package certgraphanalysis
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/google/go-cmp/cmp"
8+
)
9+
10+
func TestDurationToHumanReadableString(t *testing.T) {
11+
tests := []struct {
12+
duration time.Duration
13+
expected string
14+
}{
15+
{0, "0s"},
16+
{time.Second, "1s"},
17+
{2 * time.Second, "2s"},
18+
{time.Minute, "1m"},
19+
{time.Minute + 30*time.Second, "1m30s"},
20+
{time.Hour, "1h"},
21+
{25 * time.Hour, "1d1h"},
22+
{30 * 24 * time.Hour, "1mo"},
23+
{365 * 24 * time.Hour, "1y"},
24+
{400 * 24 * time.Hour, "1y1mo5d"},
25+
{-time.Minute, "1m"}, // negative duration
26+
{-400 * 24 * time.Hour, "1y1mo5d"}, // negative composite
27+
{3*time.Minute + 4*time.Second, "3m4s"},
28+
}
29+
for _, test := range tests {
30+
t.Run(test.expected, func(t *testing.T) {
31+
result := durationToHumanReadableString(test.duration)
32+
if result != test.expected {
33+
t.Errorf("expected %s, got %s", test.expected, result)
34+
}
35+
})
36+
}
37+
}
38+
39+
func TestHumanizeRefreshPeriodFromMetadata(t *testing.T) {
40+
tests := []struct {
41+
metadata string
42+
expected string
43+
}{
44+
{
45+
metadata: "72h00m00s",
46+
expected: "3d",
47+
},
48+
{
49+
metadata: "124h25m00s",
50+
expected: "5d4h25m",
51+
},
52+
{
53+
metadata: "82080h00m00s",
54+
expected: "9y4mo15d",
55+
},
56+
}
57+
for _, test := range tests {
58+
t.Run(test.metadata, func(t *testing.T) {
59+
result := map[string]string{
60+
"certificates.openshift.io/refresh-period": test.metadata,
61+
}
62+
humanizeRefreshPeriodFromMetadata(result)
63+
64+
expected := map[string]string{
65+
"certificates.openshift.io/refresh-period": test.expected,
66+
"rewritten.cert-info.openshift.io/RewriteRefreshPeriod": test.metadata,
67+
}
68+
diff := cmp.Diff(expected, result)
69+
if diff != "" {
70+
t.Errorf("expected %v, got %v, diff: %s", test.expected, result, diff)
71+
}
72+
})
73+
}
74+
}
75+
76+
func TestHumanizeRefreshPeriodFromMetadataNils(t *testing.T) {
77+
humanizeRefreshPeriodFromMetadata(nil)
78+
humanizeRefreshPeriodFromMetadata(map[string]string{})
79+
}

0 commit comments

Comments
 (0)