diff --git a/service/status/help_test.go b/service/status/help_test.go index a5e00ac6..9e34d074 100644 --- a/service/status/help_test.go +++ b/service/status/help_test.go @@ -48,7 +48,8 @@ func testStatus() (status *Status) { &dashboard.Options{ Icon: "https://example.com/icon.png", IconLinkTo: "https://example.com/icon-link", - WebURL: "https://example.com"}) + WebURL: "https://example.com", + Tags: []string{"foo", "bar"}}) status.SetApprovedVersion("1.1.1", false) status.SetLatestVersion("2.2.2", "2002-02-02T02:02:02Z", false) diff --git a/service/status/info/info.go b/service/status/info/info.go index 29de6e73..f9241412 100644 --- a/service/status/info/info.go +++ b/service/status/info/info.go @@ -19,7 +19,7 @@ import "sync" // ServiceInfo holds information about a service. type ServiceInfo struct { - mutex *sync.RWMutex `json:"-"` // Mutex for thread-safe access + mutex *sync.RWMutex // Mutex for thread-safe access ID string `json:"id,omitempty"` // Service ID Name string `json:"name,omitempty"` // Service name @@ -32,6 +32,8 @@ type ServiceInfo struct { ApprovedVersion string `json:"approved_version,omitempty"` // The version of the Service that has been approved for deployment. DeployedVersion string `json:"deployed_version,omitempty"` // The version of the Service that is deployed. LatestVersion string `json:"latest_version,omitempty"` // The latest version of the Service found from query(). + + Tags []string `json:"tags,omitempty"` // Tags for the Service. } // SetMutex sets the mutex pointer for thread-safe access. diff --git a/service/status/status.go b/service/status/status.go index 277746ae..d5f8d334 100644 --- a/service/status/status.go +++ b/service/status/status.go @@ -248,6 +248,7 @@ func (s *Status) Init( s.ServiceInfo.ID = serviceID s.ServiceInfo.Name = serviceName s.ServiceInfo.URL = serviceURL + s.ServiceInfo.Tags = dashboard.Tags s.Dashboard = dashboard s.ServiceInfo.SetMutex(&s.mutex) diff --git a/service/status/status_test.go b/service/status/status_test.go index 7a7ca487..36bbe02e 100644 --- a/service/status/status_test.go +++ b/service/status/status_test.go @@ -51,11 +51,12 @@ func TestStatus_Unmarshal(t *testing.T) { // WHEN UnmarshalX is called on the Status. var status Status - if tc.format == "YAML" { + switch tc.format { + case "YAML": var node yaml.Node - status.UnmarshalYAML(&node) - } else if tc.format == "JSON" { - status.UnmarshalJSON([]byte("")) + _ = status.UnmarshalYAML(&node) + case "JSON": + _ = status.UnmarshalJSON([]byte("")) } // THEN the mutex is correctly handed to the ServiceInfo. @@ -189,6 +190,8 @@ func TestService_ServiceInfo(t *testing.T) { status.Dashboard.IconLinkTo = iconLinkTo webURL := "https://example.com/web" status.Dashboard.WebURL = webURL + tags := []string{"tag1", "tag2"} + status.ServiceInfo.Tags = tags approvedVersion := "approved.version" status.SetApprovedVersion(approvedVersion, false) @@ -210,6 +213,7 @@ func TestService_ServiceInfo(t *testing.T) { Icon: icon, IconLinkTo: iconLinkTo, WebURL: webURL, + Tags: tags, DeployedVersion: deployedVersion, ApprovedVersion: approvedVersion, diff --git a/util/help_test.go b/util/help_test.go index f343a21b..ef08358d 100644 --- a/util/help_test.go +++ b/util/help_test.go @@ -16,7 +16,9 @@ package util -import serviceinfo "github.com/release-argus/Argus/service/status/info" +import ( + serviceinfo "github.com/release-argus/Argus/service/status/info" +) var packageName = "util" @@ -33,5 +35,6 @@ func testServiceInfo() serviceinfo.ServiceInfo { ApprovedVersion: "APPROVED", DeployedVersion: "DEPLOYED", LatestVersion: "NEW", + Tags: []string{"tag1", "tag2"}, } } diff --git a/util/template.go b/util/template.go index a17829e0..4c2ec6da 100644 --- a/util/template.go +++ b/util/template.go @@ -50,10 +50,11 @@ func TemplateString(template string, context serviceinfo.ServiceInfo) string { "icon": context.Icon, "icon_link_to": context.IconLinkTo, "web_url": context.WebURL, - "version": context.LatestVersion, "approved_version": context.ApprovedVersion, "deployed_version": context.DeployedVersion, + "version": context.LatestVersion, "latest_version": context.LatestVersion, + "tags": context.Tags, }) if err != nil { panic(err) diff --git a/util/template_test.go b/util/template_test.go index e6c46cfc..baff9dee 100644 --- a/util/template_test.go +++ b/util/template_test.go @@ -69,16 +69,29 @@ func TestTemplate_String(t *testing.T) { WebURL: svcInfo.WebURL, LatestVersion: svcInfo.LatestVersion}, }, + "valid django template with array access": { + template: "{{ tags | first }}-{{ tags.0 }}_{{ tags|slice:'1:2'|first }}-{{ tags | last }}-{{ tags.1 }}_{{ tags | join:',' }}", + want: fmt.Sprintf("%s-%s_%s-%s-%s_%s,%s", + svcInfo.Tags[0], svcInfo.Tags[0], + svcInfo.Tags[1], svcInfo.Tags[1], svcInfo.Tags[1], + svcInfo.Tags[0], svcInfo.Tags[1]), + serviceInfo: svcInfo}, + "valid django template with array access out of bounds": { + template: "{{ tags.0 }}-{{ tags.1 }}-{{ tags.2 }}-{{ tags.3 }}", + want: fmt.Sprintf("%s-%s--", + svcInfo.Tags[0], svcInfo.Tags[1]), + serviceInfo: svcInfo}, "invalid django template panic": { template: "-{% 'a' == 'a' %}{{ service_id }}{% endif %}-{{ service_url }}-{{ web_url }}-{{ version }}", panicRegex: test.StringPtr("Tag name must be an identifier"), serviceInfo: svcInfo}, "all django vars": { - template: "{{ service_id }}-{{ service_name }}-{{ service_url }}--{{ icon }}-{{ icon_link_to }}-{{ web_url }}--{{ version }}-{{ approved_version }}-{{ deployed_version }}-{{ latest_version }}", - want: fmt.Sprintf("%s-%s-%s--%s-%s-%s--%s-%s-%s-%s", + template: "{{ service_id }}-{{ service_name }}-{{ service_url }}--{{ icon }}-{{ icon_link_to }}-{{ web_url }}--{{ version }}-{{ approved_version }}-{{ deployed_version }}-{{ latest_version }}-{{ tags|first }}-{{ tags.1 }}", + want: fmt.Sprintf("%s-%s-%s--%s-%s-%s--%s-%s-%s-%s-%s-%s", svcInfo.ID, svcInfo.Name, svcInfo.URL, svcInfo.Icon, svcInfo.IconLinkTo, svcInfo.WebURL, - svcInfo.LatestVersion, svcInfo.ApprovedVersion, svcInfo.DeployedVersion, svcInfo.LatestVersion), + svcInfo.LatestVersion, svcInfo.ApprovedVersion, svcInfo.DeployedVersion, svcInfo.LatestVersion, + svcInfo.Tags[0], svcInfo.Tags[1]), serviceInfo: svcInfo}, } diff --git a/util/util.go b/util/util.go index 7911f82c..af3d741c 100644 --- a/util/util.go +++ b/util/util.go @@ -64,18 +64,18 @@ func ValueOrValue[T comparable](first T, second T) T { return second } -// DereferenceOrDefault returns the value of `check` if not nil, +// DereferenceOrDefault returns the value of `ptr` if not nil, // otherwise default for the type. -func DereferenceOrDefault[T comparable](check *T) T { - if check == nil { +func DereferenceOrDefault[T any](ptr *T) T { + if ptr == nil { return *new(T) } - return *check + return *ptr } // DereferenceOrValue returns the value of 'ptr' if non-nil, // otherwise the 'fallback'. -func DereferenceOrValue[T comparable](ptr *T, fallback T) T { +func DereferenceOrValue[T any](ptr *T, fallback T) T { if ptr != nil { return *ptr } @@ -83,7 +83,7 @@ func DereferenceOrValue[T comparable](ptr *T, fallback T) T { } // CopyPointer returns a pointer to a copy of the value of `ptr`. -func CopyPointer[T comparable](ptr *T) *T { +func CopyPointer[T any](ptr *T) *T { if ptr == nil { return nil } diff --git a/util/util_test.go b/util/util_test.go index 6dec487d..bfb72418 100644 --- a/util/util_test.go +++ b/util/util_test.go @@ -18,6 +18,7 @@ package util import ( "fmt" + "reflect" "strings" "testing" @@ -188,6 +189,12 @@ func TestDereferenceOrValue(t *testing.T) { "non-nil int pointer": { ptr: test.IntPtr(3), value: 2, want: 3}, + "nil string slice": { + ptr: (*[]string)(nil), + value: []string{"baz"}, want: []string{"baz"}}, + "non-nil string slice": { + ptr: test.StringSlicePtr([]string{"foo", "bar"}), + value: []string{"baz"}, want: []string{"foo", "bar"}}, } for name, tc := range tests { @@ -203,10 +210,12 @@ func TestDereferenceOrValue(t *testing.T) { got = DereferenceOrValue(v, tc.value.(bool)) case *int: got = DereferenceOrValue(v, tc.value.(int)) + case *[]string: + got = DereferenceOrValue(v, tc.value.([]string)) } // THEN the pointer is returned if it's nil, otherwise the value. - if got != tc.want { + if !reflect.DeepEqual(got, tc.want) { t.Errorf("%s\nwant: %v\ngot: %v", packageName, tc.want, got) } @@ -216,15 +225,28 @@ func TestDereferenceOrValue(t *testing.T) { func TestCopyPointer(t *testing.T) { tests := map[string]struct { - input, want *int + input any + doesCopy bool }{ "nil pointer": { - input: nil, - want: nil, + input: nil, + doesCopy: false, + }, + "non-nil int pointer": { + input: test.IntPtr(6), + doesCopy: true, + }, + "non-nil string pointer": { + input: test.StringPtr("foo"), + doesCopy: true, + }, + "non-nil bool pointer": { + input: test.BoolPtr(true), + doesCopy: true, }, - "non-nil pointer": { - input: test.IntPtr(6), - want: test.IntPtr(6), + "non-nil string slice pointer": { + input: test.StringSlicePtr([]string{"foo", "bar"}), + doesCopy: true, }, } @@ -233,14 +255,24 @@ func TestCopyPointer(t *testing.T) { t.Parallel() // WHEN CopyPointer is called. - got := CopyPointer(tc.input) + var got any + switch v := tc.input.(type) { + case *string: + got = CopyPointer(v) + case *bool: + got = CopyPointer(v) + case *int: + got = CopyPointer(v) + case *[]string: + got = CopyPointer(v) + } // THEN the result should be a pointer to a copy of the value. - if (tc.want != nil && got == nil) || - (tc.want == nil && got != nil) || - (got != nil && *tc.want != *got) { + if (tc.doesCopy && got == nil) || + (tc.doesCopy && !reflect.DeepEqual(got, tc.input)) || + (!tc.doesCopy && got != nil) { t.Errorf("%s\nwant %v, got %v", - packageName, tc.want, got) + packageName, tc.input, got) } }) }