diff --git a/go.mod b/go.mod index 8e2e1792445..c9a91001aec 100644 --- a/go.mod +++ b/go.mod @@ -71,7 +71,7 @@ require ( github.com/googleapis/gax-go/v2 v2.14.1 // @grafana/grafana-backend-group github.com/gorilla/mux v1.8.1 // @grafana/grafana-backend-group github.com/gorilla/websocket v1.5.3 // @grafana/grafana-app-platform-squad - github.com/grafana/alerting v0.0.0-20250219153626-c475b1a572b0 // @grafana/alerting-backend + github.com/grafana/alerting v0.0.0-20250220212119-4baca04e46bb // @grafana/alerting-backend github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7 // @grafana/identity-access-team github.com/grafana/authlib/types v0.0.0-20250219092154-21ce22b49f31 // @grafana/identity-access-team github.com/grafana/dataplane/examples v0.0.1 // @grafana/observability-metrics diff --git a/go.sum b/go.sum index 88780f81fc9..a467f87cba7 100644 --- a/go.sum +++ b/go.sum @@ -1511,8 +1511,8 @@ github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7Fsg github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grafana/alerting v0.0.0-20250219153626-c475b1a572b0 h1:LGH+tVzHCDrR9hsltmkP4jmNRg5IreQw5CNFbJKlnts= -github.com/grafana/alerting v0.0.0-20250219153626-c475b1a572b0/go.mod h1:hdGB3dSl8Ma9Rjo2YiAEAjMkZ5HiNJbNDqRKDefRZrM= +github.com/grafana/alerting v0.0.0-20250220212119-4baca04e46bb h1:WfCsiuZXhGXIdzImQ9/Kjfn9M4e6f7z5mddcSKCRxmI= +github.com/grafana/alerting v0.0.0-20250220212119-4baca04e46bb/go.mod h1:hdGB3dSl8Ma9Rjo2YiAEAjMkZ5HiNJbNDqRKDefRZrM= github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7 h1:NTMmow+74I3Jb033xhbRgWQS7A//5TDhiM4tl7bsVP4= github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7/go.mod h1:T3X4z0ejGfJOiOmZLFeKCRT/yxWJq/RtclAc/PHj/w4= github.com/grafana/authlib/types v0.0.0-20250219092154-21ce22b49f31 h1:EokLC5grHwLPs4tXW8T6E8187H1e5G9AP0QQ5B60HbA= diff --git a/pkg/services/ngalert/api/compat_contact_points.go b/pkg/services/ngalert/api/compat_contact_points.go index d10c3859598..1299af0bb64 100644 --- a/pkg/services/ngalert/api/compat_contact_points.go +++ b/pkg/services/ngalert/api/compat_contact_points.go @@ -91,6 +91,13 @@ func ContactPointToContactPointExport(cp definitions.ContactPoint) (notify.APIRe } integration = append(integration, el) } + for _, i := range cp.Jira { + el, err := marshallIntegration(j, "jira", i, i.DisableResolveMessage) + if err != nil { + errs = append(errs, err) + } + integration = append(integration, el) + } for _, i := range cp.Kafka { el, err := marshallIntegration(j, "kafka", i, i.DisableResolveMessage) if err != nil { @@ -271,6 +278,11 @@ func parseIntegration(json jsoniter.API, result *definitions.ContactPoint, recei if err = json.Unmarshal(data, &integration); err == nil { result.Googlechat = append(result.Googlechat, integration) } + case "jira": + integration := definitions.JiraIntegration{DisableResolveMessage: disable} + if err = json.Unmarshal(data, &integration); err == nil { + result.Jira = append(result.Jira, integration) + } case "kafka": integration := definitions.KafkaIntegration{DisableResolveMessage: disable} if err = json.Unmarshal(data, &integration); err == nil { diff --git a/pkg/services/ngalert/api/tooling/definitions/contact_points.go b/pkg/services/ngalert/api/tooling/definitions/contact_points.go index 9df9b8ee444..498ac6f0d37 100644 --- a/pkg/services/ngalert/api/tooling/definitions/contact_points.go +++ b/pkg/services/ngalert/api/tooling/definitions/contact_points.go @@ -62,6 +62,29 @@ type GooglechatIntegration struct { Message *string `json:"message,omitempty" yaml:"message,omitempty" hcl:"message"` } +type JiraIntegration struct { + DisableResolveMessage *bool `json:"-" yaml:"-" hcl:"disable_resolve_message"` + + URL string `yaml:"api_url,omitempty" json:"api_url,omitempty" hcl:"api_url"` + Project string `yaml:"project,omitempty" json:"project,omitempty" hcl:"project"` + IssueType string `yaml:"issue_type,omitempty" json:"issue_type,omitempty" hcl:"issue_type"` + + Summary *string `yaml:"summary,omitempty" json:"summary,omitempty" hcl:"summary"` + Description *string `yaml:"description,omitempty" json:"description,omitempty" hcl:"description"` + Labels *[]string `yaml:"labels,omitempty" json:"labels,omitempty" hcl:"labels"` + Priority *string `yaml:"priority,omitempty" json:"priority,omitempty" hcl:"priority"` + ReopenTransition *string `yaml:"reopen_transition,omitempty" json:"reopen_transition,omitempty" hcl:"reopen_transition"` + ResolveTransition *string `yaml:"resolve_transition,omitempty" json:"resolve_transition,omitempty" hcl:"resolve_transition"` + WontFixResolution *string `yaml:"wont_fix_resolution,omitempty" json:"wont_fix_resolution,omitempty" hcl:"wont_fix_resolution"` + ReopenDuration *string `yaml:"reopen_duration,omitempty" json:"reopen_duration,omitempty" hcl:"reopen_duration"` + DedupKeyFieldName *string `yaml:"dedup_key_field,omitempty" json:"dedup_key_field,omitempty" hcl:"dedup_key_field"` + Fields *map[string]any `yaml:"fields,omitempty" json:"fields,omitempty" hcl:"fields"` + + User *Secret `yaml:"user,omitempty" json:"user,omitempty" hcl:"user"` + Password *Secret `yaml:"password,omitempty" json:"password,omitempty" hcl:"password"` + Token *Secret `yaml:"api_token,omitempty" json:"api_token,omitempty" hcl:"api_token"` +} + type KafkaIntegration struct { DisableResolveMessage *bool `json:"-" yaml:"-" hcl:"disable_resolve_message"` @@ -321,6 +344,7 @@ type ContactPoint struct { Discord []DiscordIntegration `json:"discord" yaml:"discord" hcl:"discord,block"` Email []EmailIntegration `json:"email" yaml:"email" hcl:"email,block"` Googlechat []GooglechatIntegration `json:"googlechat" yaml:"googlechat" hcl:"googlechat,block"` + Jira []JiraIntegration `json:"jira" yaml:"jira" hcl:"jira,block"` Kafka []KafkaIntegration `json:"kafka" yaml:"kafka" hcl:"kafka,block"` Line []LineIntegration `json:"line" yaml:"line" hcl:"line,block"` Mqtt []MqttIntegration `json:"mqtt" yaml:"mqtt" hcl:"mqtt,block"` diff --git a/pkg/services/ngalert/notifier/channels_config/available_channels.go b/pkg/services/ngalert/notifier/channels_config/available_channels.go index e1748a7550b..a64c641a40e 100644 --- a/pkg/services/ngalert/notifier/channels_config/available_channels.go +++ b/pkg/services/ngalert/notifier/channels_config/available_channels.go @@ -5,6 +5,7 @@ import ( "os" "strings" + "github.com/grafana/alerting/receivers/jira" alertingMqtt "github.com/grafana/alerting/receivers/mqtt" alertingOpsgenie "github.com/grafana/alerting/receivers/opsgenie" alertingPagerduty "github.com/grafana/alerting/receivers/pagerduty" @@ -365,7 +366,7 @@ func GetAvailableNotifiers() []*NotifierPlugin { InputType: InputTypeText, PropertyName: "details", }, - { //New in 11.1 + { // New in 11.1 Label: "URL", Description: "The URL to send API requests to", Element: ElementTypeInput, @@ -1708,6 +1709,154 @@ func GetAvailableNotifiers() []*NotifierPlugin { }, }, }, + { // Since Grafana 11.6 + Type: "jira", + Name: "Jira", + Description: "Creates Jira issues from alerts", + Heading: "Jira settings", + Options: []NotifierOption{ + { + Label: "API URL of Jira instance, including version of API", + Element: ElementTypeInput, + InputType: InputTypeText, + Placeholder: "https://grafana.atlassian.net/rest/api/3", + PropertyName: "api_url", + Description: "Supported v2 or v3 APIs", + Required: true, + }, + { + Label: "HTTP Basic Authentication - Username", + Element: ElementTypeInput, + InputType: InputTypeText, + PropertyName: "user", + Description: "Username to use for Jira authentication.", + Secure: true, + Required: false, + }, + { + Label: "HTTP Basic Authentication - Password", + Element: ElementTypeInput, + InputType: InputTypePassword, + PropertyName: "password", + // Go to https://id.atlassian.com/manage-profile/security/api-tokens to obtain a token. + Description: "Password to use for Jira authentication.", + Secure: true, + Required: false, + }, + { + Label: "Authorization Header - Personal Access Token", + Element: ElementTypeInput, + InputType: InputTypePassword, + PropertyName: "api_token", + // Go to https://confluence.atlassian.com/enterprise/using-personal-access-tokens-1026032365.html for how to obtain a token. + Description: "Personal Access Token that is used as a bearer authorization header.", + Secure: true, + Required: false, + }, + { + Label: "Project Key", + Description: "The project key associated with the relevant Jira project", + Element: ElementTypeInput, + InputType: InputTypeText, + Placeholder: "Grafana", + PropertyName: "project", + Required: true, + }, + { + Label: "Issue Type", + Description: "The type of the Jira issue (e.g., Bug, Task, Story). You can use templates to customize this field.", + Element: ElementTypeInput, + InputType: InputTypeText, + Placeholder: "Task", + Required: true, + PropertyName: "issue_type", + }, + { + Label: "Summary", + Description: fmt.Sprintf("The summary of the Jira issue. You can use templates to customize this field. Maximum length is %d characters.", jira.MaxSummaryLenRunes), + Element: ElementTypeInput, + InputType: InputTypeText, + Placeholder: jira.DefaultSummary, + PropertyName: "summary", + }, + { + Label: "Description", + Description: fmt.Sprintf("The description of the Jira issue. You can use templates to customize this field. Maximum length is %d characters.", jira.MaxDescriptionLenRunes), + Element: ElementTypeTextArea, + InputType: InputTypeText, + Placeholder: jira.DefaultDescription, + PropertyName: "description", + }, + { + Label: "Labels", + Description: "Labels to assign to the Jira issue. You can use templates to customize this field.", + Element: ElementStringArray, + Placeholder: "", + PropertyName: "labels", + }, + { + Label: "Priority", + Description: "The priority of the Jira issue (e.g., High, Medium, Low). You can use templates to customize this field.", + Element: ElementTypeInput, + InputType: InputTypeText, + Placeholder: jira.DefaultPriority, + PropertyName: "priority", + Required: false, + }, + { + Label: "Resolve Transition", + Description: `Name of the workflow transition to resolve an issue. The target status must have the category "done". If not set, the issue will not be resolved.`, + Element: ElementTypeInput, + InputType: InputTypeText, + Placeholder: "", + PropertyName: "resolve_transition", + Required: false, + }, + { + Label: "Reopen Transition", + Description: `Name of the workflow transition to resolve an issue. The target status must not have the category "done". If not set, the issue will not be reopened.`, + Element: ElementTypeInput, + InputType: InputTypeText, + Placeholder: "", + PropertyName: "reopen_transition", + Required: false, + }, + { + Label: "Reopen Duration", + Description: "Reopen the issue when it is not older than this value in minutes. Otherwise, create a new issue.", + Element: ElementTypeInput, + InputType: InputTypeText, + Placeholder: "10m", + PropertyName: "reopen_duration", + }, + { + Label: "\"Won't fix\" Transition", + Description: `If reopen transition is defined, ignore issues with that resolution.`, + Element: ElementTypeInput, + InputType: InputTypeText, + Placeholder: "", + PropertyName: "wont_fix_resolution", + Required: false, + }, + { + Label: "Custom field ID for deduplication", + Description: "Id of the custom field where the deduplication key should be stored. Otherwise, it is added to labels in format 'ALERT($KEY).'", + Element: ElementTypeInput, + InputType: InputTypeText, + Placeholder: "10000", + ValidationRule: "^[0-9]+$", + PropertyName: "dedup_key_field", + }, + { + Label: "Custom Field Data", + Description: "Custom field data to set on the Jira issue.", + Element: ElementTypeKeyValueMap, + InputType: InputTypeText, + Placeholder: "", + PropertyName: "fields", + }, + }, + }, } } diff --git a/pkg/services/ngalert/notifier/channels_config/available_channels_test.go b/pkg/services/ngalert/notifier/channels_config/available_channels_test.go index 5e305cb1224..cc2defa2c80 100644 --- a/pkg/services/ngalert/notifier/channels_config/available_channels_test.go +++ b/pkg/services/ngalert/notifier/channels_config/available_channels_test.go @@ -27,20 +27,38 @@ func TestGetSecretKeysForContactPointType(t *testing.T) { {receiverType: "prometheus-alertmanager", expectedSecretFields: []string{"basicAuthPassword"}}, {receiverType: "discord", expectedSecretFields: []string{"url"}}, {receiverType: "googlechat", expectedSecretFields: []string{"url"}}, - {receiverType: "line", expectedSecretFields: []string{"token"}}, + {receiverType: "LINE", expectedSecretFields: []string{"token"}}, {receiverType: "threema", expectedSecretFields: []string{"api_secret"}}, {receiverType: "opsgenie", expectedSecretFields: []string{"apiKey"}}, {receiverType: "webex", expectedSecretFields: []string{"bot_token"}}, {receiverType: "sns", expectedSecretFields: []string{"sigv4.access_key", "sigv4.secret_key"}}, + {receiverType: "mqtt", expectedSecretFields: []string{"password", "tlsConfig.caCertificate", "tlsConfig.clientCertificate", "tlsConfig.clientKey"}}, + {receiverType: "jira", expectedSecretFields: []string{"user", "password", "api_token"}}, } + n := GetAvailableNotifiers() + allTypes := make(map[string]struct{}, len(n)) + for _, plugin := range n { + allTypes[plugin.Type] = struct{}{} + } + for _, testCase := range testCases { + delete(allTypes, testCase.receiverType) t.Run(testCase.receiverType, func(t *testing.T) { got, err := GetSecretKeysForContactPointType(testCase.receiverType) require.NoError(t, err) - t.Logf("got secret fields: %#v", got) require.ElementsMatch(t, testCase.expectedSecretFields, got) }) } + + for integrationType := range allTypes { + t.Run(integrationType, func(t *testing.T) { + got, err := GetSecretKeysForContactPointType(integrationType) + require.NoError(t, err) + require.Emptyf(t, got, "secret keys for %s should be empty", integrationType) + }) + } + + require.Emptyf(t, allTypes, "not all types are covered: %s", allTypes) } func Test_getSecretFields(t *testing.T) { diff --git a/pkg/services/ngalert/notifier/channels_config/plugin.go b/pkg/services/ngalert/notifier/channels_config/plugin.go index f390ee1d9fc..5a8b7527604 100644 --- a/pkg/services/ngalert/notifier/channels_config/plugin.go +++ b/pkg/services/ngalert/notifier/channels_config/plugin.go @@ -45,6 +45,8 @@ const ( ElementTypeSubform = "subform" // ElementSubformArray will render a multiple sub-forms with schema defined in SubformOptions ElementSubformArray = "subform_array" + // ElementStringArray will render a set of fields to manage an array of strings. + ElementStringArray = "string_array" ) // InputType is the type of input that can be rendered in the frontend. diff --git a/pkg/services/ngalert/provisioning/contactpoints_test.go b/pkg/services/ngalert/provisioning/contactpoints_test.go index 7a8c5e5bd10..cab8dd42400 100644 --- a/pkg/services/ngalert/provisioning/contactpoints_test.go +++ b/pkg/services/ngalert/provisioning/contactpoints_test.go @@ -423,6 +423,9 @@ func TestRemoveSecretsForContactPoint(t *testing.T) { "webhook": func(settings map[string]any) { // add additional field to the settings because valid config does not allow it to be specified along with password settings["authorization_credentials"] = "test-authz-creds" }, + "jira": func(settings map[string]any) { // add additional field to the settings because valid config does not allow it to be specified along with password + settings["api_token"] = "test-token" + }, } configs := notify.AllKnownConfigsForTesting diff --git a/pkg/storage/unified/apistore/go.mod b/pkg/storage/unified/apistore/go.mod index d99962d0d35..16380887a73 100644 --- a/pkg/storage/unified/apistore/go.mod +++ b/pkg/storage/unified/apistore/go.mod @@ -192,7 +192,7 @@ require ( github.com/googleapis/gax-go/v2 v2.14.1 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/websocket v1.5.3 // indirect - github.com/grafana/alerting v0.0.0-20250219153626-c475b1a572b0 // indirect + github.com/grafana/alerting v0.0.0-20250220212119-4baca04e46bb // indirect github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7 // indirect github.com/grafana/dataplane/sdata v0.0.9 // indirect github.com/grafana/dskit v0.0.0-20241105154643-a6b453a88040 // indirect diff --git a/pkg/storage/unified/apistore/go.sum b/pkg/storage/unified/apistore/go.sum index 4d78ed949da..40c89cf0360 100644 --- a/pkg/storage/unified/apistore/go.sum +++ b/pkg/storage/unified/apistore/go.sum @@ -566,8 +566,8 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grafana/alerting v0.0.0-20250219153626-c475b1a572b0 h1:LGH+tVzHCDrR9hsltmkP4jmNRg5IreQw5CNFbJKlnts= -github.com/grafana/alerting v0.0.0-20250219153626-c475b1a572b0/go.mod h1:hdGB3dSl8Ma9Rjo2YiAEAjMkZ5HiNJbNDqRKDefRZrM= +github.com/grafana/alerting v0.0.0-20250220212119-4baca04e46bb h1:WfCsiuZXhGXIdzImQ9/Kjfn9M4e6f7z5mddcSKCRxmI= +github.com/grafana/alerting v0.0.0-20250220212119-4baca04e46bb/go.mod h1:hdGB3dSl8Ma9Rjo2YiAEAjMkZ5HiNJbNDqRKDefRZrM= github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7 h1:NTMmow+74I3Jb033xhbRgWQS7A//5TDhiM4tl7bsVP4= github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7/go.mod h1:T3X4z0ejGfJOiOmZLFeKCRT/yxWJq/RtclAc/PHj/w4= github.com/grafana/authlib/types v0.0.0-20250219092154-21ce22b49f31 h1:EokLC5grHwLPs4tXW8T6E8187H1e5G9AP0QQ5B60HbA= diff --git a/pkg/storage/unified/resource/go.mod b/pkg/storage/unified/resource/go.mod index 57c54262114..f2ab921a4f6 100644 --- a/pkg/storage/unified/resource/go.mod +++ b/pkg/storage/unified/resource/go.mod @@ -117,7 +117,7 @@ require ( github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect github.com/googleapis/gax-go/v2 v2.14.1 // indirect github.com/gorilla/mux v1.8.1 // indirect - github.com/grafana/alerting v0.0.0-20250219153626-c475b1a572b0 // indirect + github.com/grafana/alerting v0.0.0-20250220212119-4baca04e46bb // indirect github.com/grafana/dataplane/sdata v0.0.9 // indirect github.com/grafana/grafana-app-sdk/logging v0.30.0 // indirect github.com/grafana/grafana-aws-sdk v0.31.5 // indirect diff --git a/pkg/storage/unified/resource/go.sum b/pkg/storage/unified/resource/go.sum index 9ee7f749839..40fcf6f458a 100644 --- a/pkg/storage/unified/resource/go.sum +++ b/pkg/storage/unified/resource/go.sum @@ -397,8 +397,8 @@ github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z github.com/gorilla/mux v1.7.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/grafana/alerting v0.0.0-20250219153626-c475b1a572b0 h1:LGH+tVzHCDrR9hsltmkP4jmNRg5IreQw5CNFbJKlnts= -github.com/grafana/alerting v0.0.0-20250219153626-c475b1a572b0/go.mod h1:hdGB3dSl8Ma9Rjo2YiAEAjMkZ5HiNJbNDqRKDefRZrM= +github.com/grafana/alerting v0.0.0-20250220212119-4baca04e46bb h1:WfCsiuZXhGXIdzImQ9/Kjfn9M4e6f7z5mddcSKCRxmI= +github.com/grafana/alerting v0.0.0-20250220212119-4baca04e46bb/go.mod h1:hdGB3dSl8Ma9Rjo2YiAEAjMkZ5HiNJbNDqRKDefRZrM= github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7 h1:NTMmow+74I3Jb033xhbRgWQS7A//5TDhiM4tl7bsVP4= github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7/go.mod h1:T3X4z0ejGfJOiOmZLFeKCRT/yxWJq/RtclAc/PHj/w4= github.com/grafana/authlib/types v0.0.0-20250219092154-21ce22b49f31 h1:EokLC5grHwLPs4tXW8T6E8187H1e5G9AP0QQ5B60HbA= diff --git a/pkg/tests/apis/alerting/notifications/receivers/receiver_test.go b/pkg/tests/apis/alerting/notifications/receivers/receiver_test.go index 283d996f177..c483b6938ae 100644 --- a/pkg/tests/apis/alerting/notifications/receivers/receiver_test.go +++ b/pkg/tests/apis/alerting/notifications/receivers/receiver_test.go @@ -1392,9 +1392,15 @@ func TestIntegrationCRUD(t *testing.T) { t.Run("should return secrets in secureFields but not settings", func(t *testing.T) { for _, integration := range get.Spec.Integrations { t.Run(integration.Type, func(t *testing.T) { + expected := notify.AllKnownConfigsForTesting[strings.ToLower(integration.Type)] + var fields map[string]any + require.NoError(t, json.Unmarshal([]byte(expected.Config), &fields)) secretFields, err := channels_config.GetSecretKeysForContactPointType(integration.Type) require.NoError(t, err) for _, field := range secretFields { + if _, ok := fields[field]; !ok { // skip field that is not in the original setting + continue + } assert.Contains(t, integration.SecureFields, field) assert.Truef(t, integration.SecureFields[field], "secure field should be always true")