Update team integrations to reference global integrations (part of failing policies automation support) (#6156)

This commit is contained in:
Martin Angers 2022-06-13 10:04:47 -04:00 committed by GitHub
parent abe33f1d8d
commit 7f9bb6431e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 669 additions and 484 deletions

View File

@ -5505,18 +5505,14 @@ _Available in Fleet Premium_
|     destination_url | string | body | The URL to deliver the webhook requests to. |
|     policy_ids | array | body | List of policy IDs to enable failing policies webhook. |
|     host_batch_size | integer | body | Maximum number of hosts to batch on failing policy webhook requests. The default, 0, means no batching (all hosts failing a policy are sent on one request). |
| integrations | object | body | Integrations settings for the team. |
| integrations | object | body | Integrations settings for the team. Note that integrations referenced here must already exist at the global level, created by a call to [Modify configuration](#modify-configuration). |
|   jira | array | body | Jira integrations configuration. |
|     url | string | body | The URL of the Jira server to integrate with. |
|     username | string | body | The Jira username to use for this Jira integration. |
|     api_token | string | body | The API token of the Jira username to use for this Jira integration. |
|     project_key | string | body | The Jira project key to use for this integration. Jira tickets will be created in this project. |
|     url | string | body | The URL of the Jira server to use. |
|     project_key | string | body | The project key of the Jira integration to use. Jira tickets will be created in this project. |
|     enable_failing_policies | boolean | body | Whether or not that Jira integration is enabled for failing policies. Only one failing policy automation can be enabled at a given time (enable_failing_policies_webhook and enable_failing_policies). |
|   zendesk | array | body | Zendesk integrations configuration. |
|     url | string | body | The URL of the Zendesk server to integrate with. |
|     email | string | body | The Zendesk user email to use for this Zendesk integration. |
|     api_token | string | body | The Zendesk API token to use for this Zendesk integration. |
|     group_id | integer | body | The Zendesk group id to use for this integration. Zendesk tickets will be created in this group. |
|     url | string | body | The URL of the Zendesk server to use. |
|     group_id | integer | body | The Zendesk group id to use. Zendesk tickets will be created in this group. |
|     enable_failing_policies | boolean | body | Whether or not that Zendesk integration is enabled for failing policies. Only one failing policy automation can be enabled at a given time (enable_failing_policies_webhook and enable_failing_policies). |
#### Example (add users to a team)

View File

@ -46,17 +46,3 @@ func NewService(
license: license,
}, nil
}
// TODO(mna): copied from server/service/transport_error.go for now, should
// eventually have common implementations of HTTP-related errors. #4406
type badRequestError struct {
message string
}
func (e *badRequestError) Error() string {
return e.message
}
func (e *badRequestError) BadRequestError() []map[string]string {
return nil
}

View File

@ -4,7 +4,6 @@ import (
"context"
"database/sql"
"encoding/json"
"errors"
"fmt"
"github.com/fleetdm/fleet/v4/server"
@ -96,30 +95,21 @@ func (svc *Service) ModifyTeam(ctx context.Context, teamID uint, payload fleet.T
}
if payload.Integrations != nil {
oriJiraByProjectKey, err := fleet.IndexTeamJiraIntegrations(team.Config.Integrations.Jira)
// the team integrations must reference an existing global config integration.
appCfg, err := svc.ds.AppConfig(ctx)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "modify Team")
return nil, err
}
if _, err := payload.Integrations.MatchWithIntegrations(appCfg.Integrations); err != nil {
return nil, fleet.NewInvalidArgumentError("integrations", err.Error())
}
oriZendeskByGroupID, err := fleet.IndexTeamZendeskIntegrations(team.Config.Integrations.Zendesk)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "modify Team")
// integrations must be unique
if err := payload.Integrations.Validate(); err != nil {
return nil, fleet.NewInvalidArgumentError("integrations", err.Error())
}
if err := fleet.ValidateTeamJiraIntegrations(ctx, oriJiraByProjectKey, payload.Integrations.Jira); err != nil {
if errors.As(err, &fleet.IntegrationTestError{}) {
return nil, ctxerr.Wrap(ctx, &badRequestError{message: err.Error()})
}
return nil, ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("Jira integration", err.Error()))
}
team.Config.Integrations.Jira = payload.Integrations.Jira
if err := fleet.ValidateTeamZendeskIntegrations(ctx, oriZendeskByGroupID, payload.Integrations.Zendesk); err != nil {
if errors.As(err, &fleet.IntegrationTestError{}) {
return nil, ctxerr.Wrap(ctx, &badRequestError{message: err.Error()})
}
return nil, ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("Zendesk integration", err.Error()))
}
team.Config.Integrations.Zendesk = payload.Integrations.Zendesk
}

View File

@ -300,3 +300,54 @@ func (ds *Datastore) TeamAgentOptions(ctx context.Context, tid uint) (*json.RawM
}
return agentOptions, nil
}
// DeleteIntegrationsFromTeams removes the deleted integrations from any team
// that uses it.
func (ds *Datastore) DeleteIntegrationsFromTeams(ctx context.Context, deletedIntgs fleet.Integrations) error {
const (
listTeams = `SELECT id, config FROM teams WHERE config IS NOT NULL`
updateTeam = `UPDATE teams SET config = ? WHERE id = ?`
)
rows, err := ds.writer.QueryxContext(ctx, listTeams)
if err != nil {
return ctxerr.Wrap(ctx, err, "query teams")
}
defer rows.Close()
for rows.Next() {
var tm fleet.Team
if err := rows.StructScan(&tm); err != nil {
return ctxerr.Wrap(ctx, err, "scan team row in struct")
}
// ignore errors, it's ok for some integrations to not match with the
// batch of deleted integrations, we're only interested in knowing if
// some did match.
if matches, _ := tm.Config.Integrations.MatchWithIntegrations(deletedIntgs); len(matches.Jira)+len(matches.Zendesk) > 0 {
delJira, _ := fleet.IndexJiraIntegrations(matches.Jira)
delZendesk, _ := fleet.IndexZendeskIntegrations(matches.Zendesk)
var keepJira []*fleet.TeamJiraIntegration
for _, tmIntg := range tm.Config.Integrations.Jira {
if _, ok := delJira[tmIntg.UniqueKey()]; !ok {
keepJira = append(keepJira, tmIntg)
}
}
var keepZendesk []*fleet.TeamZendeskIntegration
for _, tmIntg := range tm.Config.Integrations.Zendesk {
if _, ok := delZendesk[tmIntg.UniqueKey()]; !ok {
keepZendesk = append(keepZendesk, tmIntg)
}
}
tm.Config.Integrations.Jira = keepJira
tm.Config.Integrations.Zendesk = keepZendesk
if _, err := ds.writer.ExecContext(ctx, updateTeam, tm.Config, tm.ID); err != nil {
return ctxerr.Wrap(ctx, err, "update team config")
}
}
}
return rows.Err()
}

View File

@ -29,6 +29,7 @@ func TestTeams(t *testing.T) {
{"EnrollSecrets", testTeamsEnrollSecrets},
{"TeamAgentOptions", testTeamsAgentOptions},
{"TeamsDeleteRename", testTeamsDeleteRename},
{"DeleteIntegrationsFromTeams", testTeamsDeleteIntegrationsFromTeams},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
@ -318,3 +319,108 @@ func testTeamsAgentOptions(t *testing.T, ds *Datastore) {
require.NoError(t, err)
require.JSONEq(t, string(agentOptions), string(*teamAgentOptions2))
}
func testTeamsDeleteIntegrationsFromTeams(t *testing.T, ds *Datastore) {
ctx := context.Background()
urla, urlb, urlc, urld, urle, urlf, urlg :=
"http://a.com", "http://b.com", "http://c.com", "http://d.com", "http://e.com", "http://f.com", "http://g.com"
// create some teams
team1, err := ds.NewTeam(ctx, &fleet.Team{
Name: "team1",
Config: fleet.TeamConfig{
Integrations: fleet.TeamIntegrations{
Jira: []*fleet.TeamJiraIntegration{
{URL: urla, ProjectKey: "A"},
{URL: urlb, ProjectKey: "B"},
},
Zendesk: []*fleet.TeamZendeskIntegration{
{URL: urlc, GroupID: 1},
{URL: urld, GroupID: 2},
},
},
},
})
require.NoError(t, err)
team2, err := ds.NewTeam(ctx, &fleet.Team{
Name: "team2",
Config: fleet.TeamConfig{
Integrations: fleet.TeamIntegrations{
Jira: []*fleet.TeamJiraIntegration{
{URL: urla, ProjectKey: "A"},
{URL: urle, ProjectKey: "E"},
},
Zendesk: []*fleet.TeamZendeskIntegration{
{URL: urlc, GroupID: 1},
{URL: urlf, GroupID: 3},
},
},
},
})
require.NoError(t, err)
team3, err := ds.NewTeam(ctx, &fleet.Team{
Name: "team3",
Config: fleet.TeamConfig{
Integrations: fleet.TeamIntegrations{
Jira: []*fleet.TeamJiraIntegration{
{URL: urle, ProjectKey: "E"},
},
Zendesk: []*fleet.TeamZendeskIntegration{
{URL: urlf, GroupID: 3},
},
},
},
})
require.NoError(t, err)
assertIntgURLs := func(wantTm1, wantTm2, wantTm3 []string) {
// assert that the integrations' URLs of each team corresponds to the
// expected values
expected := [][]string{wantTm1, wantTm2, wantTm3}
for i, id := range []uint{team1.ID, team2.ID, team3.ID} {
tm, err := ds.Team(ctx, id)
require.NoError(t, err)
var urls []string
for _, j := range tm.Config.Integrations.Jira {
urls = append(urls, j.URL)
}
for _, z := range tm.Config.Integrations.Zendesk {
urls = append(urls, z.URL)
}
want := expected[i]
require.ElementsMatch(t, want, urls)
}
}
// delete nothing
err = ds.DeleteIntegrationsFromTeams(context.Background(), fleet.Integrations{})
require.NoError(t, err)
assertIntgURLs([]string{urla, urlb, urlc, urld}, []string{urla, urle, urlc, urlf}, []string{urle, urlf})
// delete a, b, c (in the url) so that team1 and team2 are impacted
err = ds.DeleteIntegrationsFromTeams(context.Background(), fleet.Integrations{
Jira: []*fleet.JiraIntegration{
{URL: urla, ProjectKey: "A"},
{URL: urlb, ProjectKey: "B"},
},
Zendesk: []*fleet.ZendeskIntegration{
{URL: urlc, GroupID: 1},
},
})
require.NoError(t, err)
assertIntgURLs([]string{urld}, []string{urle, urlf}, []string{urle, urlf})
// delete g, no team is impacted
err = ds.DeleteIntegrationsFromTeams(context.Background(), fleet.Integrations{
Jira: []*fleet.JiraIntegration{
{URL: urlg, ProjectKey: "G"},
},
})
require.NoError(t, err)
assertIntgURLs([]string{urld}, []string{urle, urlf}, []string{urle, urlf})
}

View File

@ -339,6 +339,9 @@ type Datastore interface {
SearchTeams(ctx context.Context, filter TeamFilter, matchQuery string, omit ...uint) ([]*Team, error)
// TeamEnrollSecrets lists the enroll secrets for the team.
TeamEnrollSecrets(ctx context.Context, teamID uint) ([]*EnrollSecret, error)
// DeleteIntegrationsFromTeams deletes integrations used by teams, as they
// are being deleted from the global configuration.
DeleteIntegrationsFromTeams(ctx context.Context, deletedIntgs Integrations) error
///////////////////////////////////////////////////////////////////////////////
// SoftwareStore

View File

@ -4,6 +4,8 @@ import (
"context"
"errors"
"fmt"
"strconv"
"strings"
"github.com/fleetdm/fleet/v4/server/service/externalsvc"
)
@ -15,64 +17,117 @@ type TeamIntegrations struct {
Zendesk []*TeamZendeskIntegration `json:"zendesk"`
}
// ToIntegrations converts a TeamIntegrations to an Integrations struct.
func (ti TeamIntegrations) ToIntegrations() Integrations {
var intgs Integrations
intgs.Jira = make([]*JiraIntegration, len(ti.Jira))
for i, j := range ti.Jira {
intgs.Jira[i] = j.ToJiraIntegration()
// MatchWithIntegrations matches the team integrations to their corresponding
// global integrations found in globalIntgs, returning the resulting
// integrations struct. It returns an error if any team integration does not
// map to a global integration, but it will still return the complete list
// of integrations that do match.
func (ti TeamIntegrations) MatchWithIntegrations(globalIntgs Integrations) (Integrations, error) {
var result Integrations
jiraIntgs, err := IndexJiraIntegrations(globalIntgs.Jira)
if err != nil {
return result, err
}
intgs.Zendesk = make([]*ZendeskIntegration, len(ti.Zendesk))
for i, z := range ti.Zendesk {
intgs.Zendesk[i] = z.ToZendeskIntegration()
zendeskIntgs, err := IndexZendeskIntegrations(globalIntgs.Zendesk)
if err != nil {
return result, err
}
return intgs
var errs []string
for _, tmJira := range ti.Jira {
key := tmJira.UniqueKey()
intg, ok := jiraIntgs[key]
if !ok {
errs = append(errs, fmt.Sprintf("unknown Jira integration for url %s and project key %s", tmJira.URL, tmJira.ProjectKey))
continue
}
intg.EnableFailingPolicies = tmJira.EnableFailingPolicies
result.Jira = append(result.Jira, &intg)
}
for _, tmZendesk := range ti.Zendesk {
key := tmZendesk.UniqueKey()
intg, ok := zendeskIntgs[key]
if !ok {
errs = append(errs, fmt.Sprintf("unknown Zendesk integration for url %s and group ID %v", tmZendesk.URL, tmZendesk.GroupID))
continue
}
intg.EnableFailingPolicies = tmZendesk.EnableFailingPolicies
result.Zendesk = append(result.Zendesk, &intg)
}
if len(errs) > 0 {
err = errors.New(strings.Join(errs, "\n"))
}
return result, err
}
// Validate validates the team integrations for uniqueness.
func (ti TeamIntegrations) Validate() error {
jira := make(map[string]*TeamJiraIntegration, len(ti.Jira))
for _, j := range ti.Jira {
key := j.UniqueKey()
if _, ok := jira[key]; ok {
return fmt.Errorf("duplicate Jira integration for url %s and project key %s", j.URL, j.ProjectKey)
}
jira[key] = j
}
zendesk := make(map[string]*TeamZendeskIntegration, len(ti.Zendesk))
for _, z := range ti.Zendesk {
key := z.UniqueKey()
if _, ok := zendesk[key]; ok {
return fmt.Errorf("duplicate Zendesk integration for url %s and group ID %v", z.URL, z.GroupID)
}
zendesk[key] = z
}
return nil
}
// TeamJiraIntegration configures an instance of an integration with the Jira
// system for a team.
type TeamJiraIntegration struct {
URL string `json:"url"`
Username string `json:"username"`
APIToken string `json:"api_token"`
ProjectKey string `json:"project_key"`
EnableFailingPolicies bool `json:"enable_failing_policies"`
}
// ToJiraIntegration converts a TeamJiraIntegration to a JiraIntegration
// struct, leaving additional fields to their zero value.
func (ti TeamJiraIntegration) ToJiraIntegration() *JiraIntegration {
return &JiraIntegration{TeamJiraIntegration: ti}
// UniqueKey returns the unique key of this integration.
func (j TeamJiraIntegration) UniqueKey() string {
return j.URL + "\n" + j.ProjectKey
}
// TeamZendeskIntegration configures an instance of an integration with the
// external Zendesk service for a team.
type TeamZendeskIntegration struct {
URL string `json:"url"`
Email string `json:"email"`
APIToken string `json:"api_token"`
GroupID int64 `json:"group_id"`
EnableFailingPolicies bool `json:"enable_failing_policies"`
}
// ToZendeskIntegration converts a TeamZendeskIntegration to a ZendeskIntegration
// struct, leaving additional fields to their zero value.
func (ti TeamZendeskIntegration) ToZendeskIntegration() *ZendeskIntegration {
return &ZendeskIntegration{TeamZendeskIntegration: ti}
// UniqueKey returns the unique key of this integration.
func (z TeamZendeskIntegration) UniqueKey() string {
return z.URL + "\n" + strconv.FormatInt(z.GroupID, 10)
}
// JiraIntegration configures an instance of an integration with the Jira
// system.
type JiraIntegration struct {
// It is a superset of TeamJiraIntegration.
TeamJiraIntegration
URL string `json:"url"`
Username string `json:"username"`
APIToken string `json:"api_token"`
ProjectKey string `json:"project_key"`
EnableFailingPolicies bool `json:"enable_failing_policies"`
EnableSoftwareVulnerabilities bool `json:"enable_software_vulnerabilities"`
}
EnableSoftwareVulnerabilities bool `json:"enable_software_vulnerabilities"`
func (j JiraIntegration) uniqueKey() string {
return j.URL + "\n" + j.ProjectKey
}
// IndexJiraIntegrations indexes the provided Jira integrations in a map keyed
// by the project key. It returns an error if a duplicate configuration is
// found for the same project key. This is typically used to index the original
// by 'URL\nProjectKey'. It returns an error if a duplicate configuration is
// found for the same combination. This is typically used to index the original
// integrations before applying the changes requested to modify the AppConfig.
//
// Note that the returned map uses non-pointer JiraIntegration struct values,
@ -80,55 +135,37 @@ type JiraIntegration struct {
// map. This is important because of how changes are merged with the original
// AppConfig when modifying it.
func IndexJiraIntegrations(jiraIntgs []*JiraIntegration) (map[string]JiraIntegration, error) {
byProjKey := make(map[string]JiraIntegration, len(jiraIntgs))
indexed := make(map[string]JiraIntegration, len(jiraIntgs))
for _, intg := range jiraIntgs {
if _, ok := byProjKey[intg.ProjectKey]; ok {
return nil, fmt.Errorf("duplicate Jira integration for project key %s", intg.ProjectKey)
key := intg.uniqueKey()
if _, ok := indexed[key]; ok {
return nil, fmt.Errorf("duplicate Jira integration for url %s and project key %s", intg.URL, intg.ProjectKey)
}
byProjKey[intg.ProjectKey] = *intg
indexed[key] = *intg
}
return byProjKey, nil
}
// IndexTeamJiraIntegrations is the same as IndexJiraIntegrations, but for
// team-specific integration structs.
func IndexTeamJiraIntegrations(teamJiraIntgs []*TeamJiraIntegration) (map[string]TeamJiraIntegration, error) {
jiraIntgs := make([]*JiraIntegration, len(teamJiraIntgs))
for i, t := range teamJiraIntgs {
jiraIntgs[i] = t.ToJiraIntegration()
}
indexed, err := IndexJiraIntegrations(jiraIntgs)
if err != nil {
return nil, err
}
teamIndexed := make(map[string]TeamJiraIntegration, len(indexed))
for k, v := range indexed {
teamIndexed[k] = v.TeamJiraIntegration
}
return teamIndexed, nil
return indexed, nil
}
// ValidateJiraIntegrations validates that the merge of the original and new
// Jira integrations does not result in any duplicate configuration, and that
// each modified or added integration can successfully connect to the external
// Jira service.
// Jira service. It returns the list of integrations that were deleted, if any.
//
// On successful return, the newJiraIntgs slice is ready to be saved - it may
// have been updated using the original integrations if the API token was
// missing.
func ValidateJiraIntegrations(ctx context.Context, oriJiraIntgsByProjKey map[string]JiraIntegration, newJiraIntgs []*JiraIntegration) error {
newByProjKey := make(map[string]*JiraIntegration, len(newJiraIntgs))
func ValidateJiraIntegrations(ctx context.Context, oriJiraIntgsIndexed map[string]JiraIntegration, newJiraIntgs []*JiraIntegration) (deleted []*JiraIntegration, err error) {
newIndexed := make(map[string]*JiraIntegration, len(newJiraIntgs))
for i, new := range newJiraIntgs {
// first check for project key uniqueness
if _, ok := newByProjKey[new.ProjectKey]; ok {
return fmt.Errorf("duplicate Jira integration for project key %s", new.ProjectKey)
// first check for uniqueness
key := new.uniqueKey()
if _, ok := newIndexed[key]; ok {
return nil, fmt.Errorf("duplicate Jira integration for url %s and project key %s", new.URL, new.ProjectKey)
}
newByProjKey[new.ProjectKey] = new
newIndexed[key] = new
// check if existing integration is being edited
if old, ok := oriJiraIntgsByProjKey[new.ProjectKey]; ok {
if old, ok := oriJiraIntgsIndexed[key]; ok {
if old == *new {
// no further validation for unchanged integration
continue
@ -143,36 +180,18 @@ func ValidateJiraIntegrations(ctx context.Context, oriJiraIntgsByProjKey map[str
// new or updated, test it
if err := makeTestJiraRequest(ctx, new); err != nil {
return fmt.Errorf("Jira integration at index %d: %w", i, err)
return nil, fmt.Errorf("Jira integration at index %d: %w", i, err)
}
}
return nil
}
// ValidateTeamJiraIntegrations applies the same validations as
// ValidateJiraIntegrations, but for team-specific integration structs.
func ValidateTeamJiraIntegrations(ctx context.Context, oriTeamJiraIntgsByProjKey map[string]TeamJiraIntegration, newTeamJiraIntgs []*TeamJiraIntegration) error {
newJiraIntgs := make([]*JiraIntegration, len(newTeamJiraIntgs))
for i, t := range newTeamJiraIntgs {
newJiraIntgs[i] = t.ToJiraIntegration()
// collect any deleted integration
for key, intg := range oriJiraIntgsIndexed {
intg := intg // do not take address of iteration variable
if _, ok := newIndexed[key]; !ok {
deleted = append(deleted, &intg)
}
}
oriJiraIntgsByProjKey := make(map[string]JiraIntegration, len(oriTeamJiraIntgsByProjKey))
for k, v := range oriTeamJiraIntgsByProjKey {
oriJiraIntgsByProjKey[k] = *v.ToJiraIntegration()
}
if err := ValidateJiraIntegrations(ctx, oriJiraIntgsByProjKey, newJiraIntgs); err != nil {
return err
}
// assign back the newJiraIntgs to newTeamJiraIntgs, as they may have been
// updated by the call and we need to pass that change back to the caller
for i, v := range newJiraIntgs {
teamJira := newTeamJiraIntgs[i]
*teamJira = v.TeamJiraIntegration
}
return nil
return deleted, nil
}
// IntegrationTestError is the type of error returned when a validation of an
@ -211,71 +230,60 @@ func makeTestJiraRequest(ctx context.Context, intg *JiraIntegration) error {
// ZendeskIntegration configures an instance of an integration with the external Zendesk service.
type ZendeskIntegration struct {
// It is a superset of TeamZendeskIntegration.
TeamZendeskIntegration
URL string `json:"url"`
Email string `json:"email"`
APIToken string `json:"api_token"`
GroupID int64 `json:"group_id"`
EnableFailingPolicies bool `json:"enable_failing_policies"`
EnableSoftwareVulnerabilities bool `json:"enable_software_vulnerabilities"`
}
EnableSoftwareVulnerabilities bool `json:"enable_software_vulnerabilities"`
func (z ZendeskIntegration) uniqueKey() string {
return z.URL + "\n" + strconv.FormatInt(z.GroupID, 10)
}
// IndexZendeskIntegrations indexes the provided Zendesk integrations in a map
// keyed by the group ID. It returns an error if a duplicate configuration is
// found for the same group ID. This is typically used to index the original
// keyed by 'URL\nGroupID'. It returns an error if a duplicate configuration is
// found for the same combination. This is typically used to index the original
// integrations before applying the changes requested to modify the AppConfig.
//
// Note that the returned map uses non-pointer ZendeskIntegration struct
// values, so that any changes to the original value does not modify the value
// in the map. This is important because of how changes are merged with the
// original AppConfig when modifying it.
func IndexZendeskIntegrations(zendeskIntgs []*ZendeskIntegration) (map[int64]ZendeskIntegration, error) {
byGroupID := make(map[int64]ZendeskIntegration, len(zendeskIntgs))
func IndexZendeskIntegrations(zendeskIntgs []*ZendeskIntegration) (map[string]ZendeskIntegration, error) {
indexed := make(map[string]ZendeskIntegration, len(zendeskIntgs))
for _, intg := range zendeskIntgs {
if _, ok := byGroupID[intg.GroupID]; ok {
return nil, fmt.Errorf("duplicate Zendesk integration for group id %v", intg.GroupID)
key := intg.uniqueKey()
if _, ok := indexed[key]; ok {
return nil, fmt.Errorf("duplicate Zendesk integration for url %s and group id %v", intg.URL, intg.GroupID)
}
byGroupID[intg.GroupID] = *intg
indexed[key] = *intg
}
return byGroupID, nil
}
// IndexTeamZendeskIntegrations is the same as IndexZendeskIntegrations, but
// for team-specific integration structs.
func IndexTeamZendeskIntegrations(teamZendeskIntgs []*TeamZendeskIntegration) (map[int64]TeamZendeskIntegration, error) {
zendeskIntgs := make([]*ZendeskIntegration, len(teamZendeskIntgs))
for i, t := range teamZendeskIntgs {
zendeskIntgs[i] = t.ToZendeskIntegration()
}
indexed, err := IndexZendeskIntegrations(zendeskIntgs)
if err != nil {
return nil, err
}
teamIndexed := make(map[int64]TeamZendeskIntegration, len(indexed))
for k, v := range indexed {
teamIndexed[k] = v.TeamZendeskIntegration
}
return teamIndexed, nil
return indexed, nil
}
// ValidateZendeskIntegrations validates that the merge of the original and
// new Zendesk integrations does not result in any duplicate configuration,
// and that each modified or added integration can successfully connect to the
// external Zendesk service.
// external Zendesk service. It returns the list of integrations that were
// deleted, if any.
//
// On successful return, the newZendeskIntgs slice is ready to be saved - it
// may have been updated using the original integrations if the API token was
// missing.
func ValidateZendeskIntegrations(ctx context.Context, oriZendeskIntgsByGroupID map[int64]ZendeskIntegration, newZendeskIntgs []*ZendeskIntegration) error {
newByGroupID := make(map[int64]*ZendeskIntegration, len(newZendeskIntgs))
func ValidateZendeskIntegrations(ctx context.Context, oriZendeskIntgsIndexed map[string]ZendeskIntegration, newZendeskIntgs []*ZendeskIntegration) (deleted []*ZendeskIntegration, err error) {
newIndexed := make(map[string]*ZendeskIntegration, len(newZendeskIntgs))
for i, new := range newZendeskIntgs {
// first check for group id uniqueness
if _, ok := newByGroupID[new.GroupID]; ok {
return fmt.Errorf("duplicate Zendesk integration for group id %v", new.GroupID)
key := new.uniqueKey()
// first check for uniqueness
if _, ok := newIndexed[key]; ok {
return nil, fmt.Errorf("duplicate Zendesk integration for url %s and group id %v", new.URL, new.GroupID)
}
newByGroupID[new.GroupID] = new
newIndexed[key] = new
// check if existing integration is being edited
if old, ok := oriZendeskIntgsByGroupID[new.GroupID]; ok {
if old, ok := oriZendeskIntgsIndexed[key]; ok {
if old == *new {
// no further validation for unchanged integration
continue
@ -290,37 +298,18 @@ func ValidateZendeskIntegrations(ctx context.Context, oriZendeskIntgsByGroupID m
// new or updated, test it
if err := makeTestZendeskRequest(ctx, new); err != nil {
return fmt.Errorf("Zendesk integration at index %d: %w", i, err)
return nil, fmt.Errorf("Zendesk integration at index %d: %w", i, err)
}
}
return nil
}
// ValidateTeamZendeskIntegrations applies the same validations as
// ValidateZendeskIntegrations, but for team-specific integration structs.
func ValidateTeamZendeskIntegrations(ctx context.Context, oriTeamZendeskIntgsByGroupID map[int64]TeamZendeskIntegration, newTeamZendeskIntgs []*TeamZendeskIntegration) error {
newZendeskIntgs := make([]*ZendeskIntegration, len(newTeamZendeskIntgs))
for i, t := range newTeamZendeskIntgs {
newZendeskIntgs[i] = t.ToZendeskIntegration()
// collect any deleted integration
for key, intg := range oriZendeskIntgsIndexed {
intg := intg // do not take address of iteration variable
if _, ok := newIndexed[key]; !ok {
deleted = append(deleted, &intg)
}
}
oriZendeskIntgsByGroupID := make(map[int64]ZendeskIntegration, len(oriTeamZendeskIntgsByGroupID))
for k, v := range oriTeamZendeskIntgsByGroupID {
oriZendeskIntgsByGroupID[k] = *v.ToZendeskIntegration()
}
if err := ValidateZendeskIntegrations(ctx, oriZendeskIntgsByGroupID, newZendeskIntgs); err != nil {
return err
}
// assign back the newZendeskIntgs to newTeamZendeskIntgs, as they may have
// been updated by the call and we need to pass that change back to the
// caller
for i, v := range newZendeskIntgs {
teamZendesk := newTeamZendeskIntgs[i]
*teamZendesk = v.TeamZendeskIntegration
}
return nil
return deleted, nil
}
func makeTestZendeskRequest(ctx context.Context, intg *ZendeskIntegration) error {
@ -422,6 +411,23 @@ func ValidateEnabledFailingPoliciesIntegrations(webhook FailingPoliciesWebhookSe
// ValidateEnabledFailingPoliciesIntegrations, but for team-specific
// integration structs.
func ValidateEnabledFailingPoliciesTeamIntegrations(webhook FailingPoliciesWebhookSettings, teamIntgs TeamIntegrations, invalid *InvalidArgumentError) {
intgs := teamIntgs.ToIntegrations()
intgs := Integrations{
Jira: make([]*JiraIntegration, len(teamIntgs.Jira)),
Zendesk: make([]*ZendeskIntegration, len(teamIntgs.Zendesk)),
}
for i, j := range teamIntgs.Jira {
intgs.Jira[i] = &JiraIntegration{
URL: j.URL,
ProjectKey: j.ProjectKey,
EnableFailingPolicies: j.EnableFailingPolicies,
}
}
for i, z := range teamIntgs.Zendesk {
intgs.Zendesk[i] = &ZendeskIntegration{
URL: z.URL,
GroupID: z.GroupID,
EnableFailingPolicies: z.EnableFailingPolicies,
}
}
ValidateEnabledFailingPoliciesIntegrations(webhook, intgs, invalid)
}

View File

@ -272,6 +272,8 @@ type SearchTeamsFunc func(ctx context.Context, filter fleet.TeamFilter, matchQue
type TeamEnrollSecretsFunc func(ctx context.Context, teamID uint) ([]*fleet.EnrollSecret, error)
type DeleteIntegrationsFromTeamsFunc func(ctx context.Context, deletedIntgs fleet.Integrations) error
type ListSoftwareForVulnDetectionFunc func(ctx context.Context, hostID uint) ([]fleet.Software, error)
type ListSoftwareVulnerabilitiesFunc func(ctx context.Context, hostIDs []uint) (map[uint][]fleet.SoftwareVulnerability, error)
@ -803,6 +805,9 @@ type DataStore struct {
TeamEnrollSecretsFunc TeamEnrollSecretsFunc
TeamEnrollSecretsFuncInvoked bool
DeleteIntegrationsFromTeamsFunc DeleteIntegrationsFromTeamsFunc
DeleteIntegrationsFromTeamsFuncInvoked bool
ListSoftwareForVulnDetectionFunc ListSoftwareForVulnDetectionFunc
ListSoftwareForVulnDetectionFuncInvoked bool
@ -1664,6 +1669,11 @@ func (s *DataStore) TeamEnrollSecrets(ctx context.Context, teamID uint) ([]*flee
return s.TeamEnrollSecretsFunc(ctx, teamID)
}
func (s *DataStore) DeleteIntegrationsFromTeams(ctx context.Context, deletedIntgs fleet.Integrations) error {
s.DeleteIntegrationsFromTeamsFuncInvoked = true
return s.DeleteIntegrationsFromTeamsFunc(ctx, deletedIntgs)
}
func (s *DataStore) ListSoftwareForVulnDetection(ctx context.Context, hostID uint) ([]fleet.Software, error) {
s.ListSoftwareForVulnDetectionFuncInvoked = true
return s.ListSoftwareForVulnDetectionFunc(ctx, hostID)

View File

@ -79,7 +79,7 @@ func TriggerFailingPoliciesAutomation(
}
// prepare the per-team configuration caches
getTeam := makeTeamConfigCache(ds)
getTeam := makeTeamConfigCache(ds, appConfig.Integrations)
policySets, err := failingPoliciesSet.ListSets()
if err != nil {
@ -150,8 +150,9 @@ func TriggerFailingPoliciesAutomation(
return nil
}
func makeTeamConfigCache(ds fleet.Datastore) func(ctx context.Context, teamID uint) (FailingPolicyAutomationConfig, error) {
func makeTeamConfigCache(ds fleet.Datastore, globalIntgs fleet.Integrations) func(ctx context.Context, teamID uint) (FailingPolicyAutomationConfig, error) {
teamCfgs := make(map[uint]FailingPolicyAutomationConfig)
return func(ctx context.Context, teamID uint) (FailingPolicyAutomationConfig, error) {
cfg, ok := teamCfgs[teamID]
if ok {
@ -163,7 +164,12 @@ func makeTeamConfigCache(ds fleet.Datastore) func(ctx context.Context, teamID ui
return cfg, ctxerr.Wrapf(ctx, err, "get team: %d", teamID)
}
teamAutomation := getActiveAutomation(team.Config.WebhookSettings.FailingPoliciesWebhook, team.Config.Integrations.ToIntegrations())
intgs, err := team.Config.Integrations.MatchWithIntegrations(globalIntgs)
if err != nil {
return cfg, ctxerr.Wrap(ctx, err, "map team integrations to global integrations")
}
teamAutomation := getActiveAutomation(team.Config.WebhookSettings.FailingPoliciesWebhook, intgs)
teamCfg := FailingPolicyAutomationConfig{
AutomationType: teamAutomation,
}

View File

@ -27,7 +27,8 @@ func TestTriggerFailingPolicies(t *testing.T) {
// pol-teamB-{7-9}: team B policies (only 7 and 8 is enabled), ids 7-8-9
// pol-teamC-10: team C policy, team does not exist, id 10
// pol-unknown-11: policy that does not exist anymore, id 11
// pol-teamD-{12-14}: team C policies (only 12 and 13 is enabled), ids 12-13-14
// pol-teamD-{12-14}: team D policies (only 12 and 13 is enabled), ids 12-13-14
// pol-teamE-15: team E policy, integration does not exist at the global level
//
// Global config uses the webhook, team A a Jira integration, team B a
// Zendesk integration, team D a webhook.
@ -46,6 +47,7 @@ func TestTriggerFailingPolicies(t *testing.T) {
12: {ID: 12, Name: "pol-teamD-12", TeamID: ptr.Uint(4)},
13: {ID: 13, Name: "pol-teamD-13", TeamID: ptr.Uint(4)},
14: {ID: 14, Name: "pol-teamD-14", TeamID: ptr.Uint(4)},
15: {ID: 15, Name: "pol-teamE-15", TeamID: ptr.Uint(5)},
}
ds.PolicyFunc = func(ctx context.Context, id uint) (*fleet.Policy, error) {
pd, ok := pols[id]
@ -64,7 +66,7 @@ func TestTriggerFailingPolicies(t *testing.T) {
},
Integrations: fleet.TeamIntegrations{
Jira: []*fleet.TeamJiraIntegration{
{EnableFailingPolicies: true},
{URL: "http://j.com", ProjectKey: "A", EnableFailingPolicies: true},
},
},
}},
@ -76,7 +78,7 @@ func TestTriggerFailingPolicies(t *testing.T) {
},
Integrations: fleet.TeamIntegrations{
Zendesk: []*fleet.TeamZendeskIntegration{
{EnableFailingPolicies: true},
{URL: "http://z.com", GroupID: 1, EnableFailingPolicies: true},
},
},
}},
@ -89,10 +91,22 @@ func TestTriggerFailingPolicies(t *testing.T) {
},
Integrations: fleet.TeamIntegrations{
Zendesk: []*fleet.TeamZendeskIntegration{
{EnableFailingPolicies: false},
{URL: "http://z.com", GroupID: 1, EnableFailingPolicies: false},
},
Jira: []*fleet.TeamJiraIntegration{
{EnableFailingPolicies: false},
{URL: "http://j.com", ProjectKey: "A", EnableFailingPolicies: false},
},
},
}},
5: {ID: 5, Name: "teamE", Config: fleet.TeamConfig{
WebhookSettings: fleet.TeamWebhookSettings{
FailingPoliciesWebhook: fleet.FailingPoliciesWebhookSettings{
PolicyIDs: []uint{15},
},
},
Integrations: fleet.TeamIntegrations{
Zendesk: []*fleet.TeamZendeskIntegration{
{URL: "http://notexist", GroupID: 999, EnableFailingPolicies: true},
},
},
}},
@ -113,6 +127,14 @@ func TestTriggerFailingPolicies(t *testing.T) {
PolicyIDs: []uint{1, 2},
},
},
Integrations: fleet.Integrations{
Jira: []*fleet.JiraIntegration{
{URL: "http://j.com", ProjectKey: "A", Username: "jirauser", APIToken: "secret"},
},
Zendesk: []*fleet.ZendeskIntegration{
{URL: "http://z.com", GroupID: 1, Email: "zendesk@z.com", APIToken: "secret"},
},
},
ServerSettings: fleet.ServerSettings{
ServerURL: "https://fleet.example.com",
},
@ -167,11 +189,17 @@ func TestTriggerFailingPolicies(t *testing.T) {
// failing policies set is now cleared
polSets, err := failingPolicySet.ListSets()
require.NoError(t, err)
var countHosts int
var remainingHosts []uint
for _, set := range polSets {
hosts, err := failingPolicySet.ListHosts(set)
require.NoError(t, err)
countHosts += len(hosts)
for _, h := range hosts {
remainingHosts = append(remainingHosts, h.ID)
}
}
require.Zero(t, countHosts)
// there's one remaining host ID in the failing policy sets, and it's the one
// with the invalid integration (it did not remove the failing policy set so
// that it can retry once the integration is fixed).
require.Len(t, remainingHosts, 1)
require.Equal(t, remainingHosts[0], uint(15)) // host id used is the same as the policy id
}

View File

@ -247,7 +247,8 @@ func (svc *Service) ModifyAppConfig(ctx context.Context, p []byte) (*fleet.AppCo
appConfig.SMTPSettings.SMTPConfigured = false
}
if err := fleet.ValidateJiraIntegrations(ctx, storedJiraByProjectKey, newAppConfig.Integrations.Jira); err != nil {
delJira, err := fleet.ValidateJiraIntegrations(ctx, storedJiraByProjectKey, newAppConfig.Integrations.Jira)
if err != nil {
if errors.As(err, &fleet.IntegrationTestError{}) {
return nil, ctxerr.Wrap(ctx, &badRequestError{message: err.Error()})
}
@ -255,7 +256,8 @@ func (svc *Service) ModifyAppConfig(ctx context.Context, p []byte) (*fleet.AppCo
}
appConfig.Integrations.Jira = newAppConfig.Integrations.Jira
if err := fleet.ValidateZendeskIntegrations(ctx, storedZendeskByGroupID, newAppConfig.Integrations.Zendesk); err != nil {
delZendesk, err := fleet.ValidateZendeskIntegrations(ctx, storedZendeskByGroupID, newAppConfig.Integrations.Zendesk)
if err != nil {
if errors.As(err, &fleet.IntegrationTestError{}) {
return nil, ctxerr.Wrap(ctx, &badRequestError{message: err.Error()})
}
@ -263,6 +265,13 @@ func (svc *Service) ModifyAppConfig(ctx context.Context, p []byte) (*fleet.AppCo
}
appConfig.Integrations.Zendesk = newAppConfig.Integrations.Zendesk
// if any integration was deleted, remove it from any team that uses it
if len(delJira)+len(delZendesk) > 0 {
if err := svc.ds.DeleteIntegrationsFromTeams(ctx, fleet.Integrations{Jira: delJira, Zendesk: delZendesk}); err != nil {
return nil, ctxerr.Wrap(ctx, err, "delete integrations from teams")
}
}
transparencyURL := appConfig.FleetDesktop.TransparencyURL
if transparencyURL != "" && license.Tier != "premium" {
invalid.Append("transparency_url", ErrMissingLicense.Error())

View File

@ -291,10 +291,10 @@ func TestAppConfigSecretsObfuscated(t *testing.T) {
SMTPSettings: fleet.SMTPSettings{SMTPPassword: "smtppassword"},
Integrations: fleet.Integrations{
Jira: []*fleet.JiraIntegration{
{TeamJiraIntegration: fleet.TeamJiraIntegration{APIToken: "jiratoken"}},
{APIToken: "jiratoken"},
},
Zendesk: []*fleet.ZendeskIntegration{
{TeamZendeskIntegration: fleet.TeamZendeskIntegration{APIToken: "zendesktoken"}},
{APIToken: "zendesktoken"},
},
},
}, nil

View File

@ -493,13 +493,42 @@ func (s *integrationEnterpriseTestSuite) TestExternalIntegrationsTeamConfig() {
require.True(t, tmResp.Team.Config.WebhookSettings.FailingPoliciesWebhook.Enable)
require.Equal(t, "http://example.com", tmResp.Team.Config.WebhookSettings.FailingPoliciesWebhook.DestinationURL)
// add an unknown automation - does not exist at the global level
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), fleet.TeamPayload{Integrations: &fleet.TeamIntegrations{
Jira: []*fleet.TeamJiraIntegration{
{
URL: srvURL,
ProjectKey: "qux",
EnableFailingPolicies: false,
},
},
}}, http.StatusUnprocessableEntity, &tmResp)
// add a couple Jira integrations at the global level (qux and qux2)
s.DoRaw("PATCH", "/api/v1/fleet/config", []byte(fmt.Sprintf(`{
"integrations": {
"jira": [
{
"url": %q,
"username": "ok",
"api_token": "foo",
"project_key": "qux"
},
{
"url": %[1]q,
"username": "ok",
"api_token": "foo",
"project_key": "qux2"
}
]
}
}`, srvURL)), http.StatusOK)
// enable an automation - should fail as the webhook is enabled too
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), fleet.TeamPayload{Integrations: &fleet.TeamIntegrations{
Jira: []*fleet.TeamJiraIntegration{
{
URL: srvURL,
Username: "ok",
APIToken: "foo",
ProjectKey: "qux",
EnableFailingPolicies: true,
},
@ -512,14 +541,12 @@ func (s *integrationEnterpriseTestSuite) TestExternalIntegrationsTeamConfig() {
require.Len(t, getResp.Team.Config.Integrations.Jira, 0)
require.Len(t, getResp.Team.Config.Integrations.Zendesk, 0)
// disable the webhook and enable the automation, should work with valid user
// disable the webhook and enable the automation
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), fleet.TeamPayload{
Integrations: &fleet.TeamIntegrations{
Jira: []*fleet.TeamJiraIntegration{
{
URL: srvURL,
Username: "ok",
APIToken: "foo",
ProjectKey: "qux",
EnableFailingPolicies: true,
},
@ -533,7 +560,7 @@ func (s *integrationEnterpriseTestSuite) TestExternalIntegrationsTeamConfig() {
},
}, http.StatusOK, &tmResp)
require.Len(t, tmResp.Team.Config.Integrations.Jira, 1)
require.Equal(t, fleet.MaskedPassword, tmResp.Team.Config.Integrations.Jira[0].APIToken)
require.Equal(t, "qux", tmResp.Team.Config.Integrations.Jira[0].ProjectKey)
// enable the webhook without changing the integration should fail (an integration is already enabled)
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), fleet.TeamPayload{WebhookSettings: &fleet.TeamWebhookSettings{
@ -549,15 +576,11 @@ func (s *integrationEnterpriseTestSuite) TestExternalIntegrationsTeamConfig() {
Jira: []*fleet.TeamJiraIntegration{
{
URL: srvURL,
Username: "ok",
APIToken: "foo",
ProjectKey: "qux",
EnableFailingPolicies: true,
},
{
URL: srvURL,
Username: "ok",
APIToken: "foo2",
ProjectKey: "qux2",
EnableFailingPolicies: false,
},
@ -565,8 +588,8 @@ func (s *integrationEnterpriseTestSuite) TestExternalIntegrationsTeamConfig() {
},
}, http.StatusOK, &tmResp)
require.Len(t, tmResp.Team.Config.Integrations.Jira, 2)
require.Equal(t, fleet.MaskedPassword, tmResp.Team.Config.Integrations.Jira[0].APIToken)
require.Equal(t, fleet.MaskedPassword, tmResp.Team.Config.Integrations.Jira[1].APIToken)
require.Equal(t, "qux", tmResp.Team.Config.Integrations.Jira[0].ProjectKey)
require.Equal(t, "qux2", tmResp.Team.Config.Integrations.Jira[1].ProjectKey)
// enabling the second without disabling the first fails
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), fleet.TeamPayload{
@ -574,15 +597,11 @@ func (s *integrationEnterpriseTestSuite) TestExternalIntegrationsTeamConfig() {
Jira: []*fleet.TeamJiraIntegration{
{
URL: srvURL,
Username: "ok",
APIToken: "foo",
ProjectKey: "qux",
EnableFailingPolicies: true,
},
{
URL: srvURL,
Username: "ok",
APIToken: "foo2",
ProjectKey: "qux2",
EnableFailingPolicies: true,
},
@ -590,65 +609,17 @@ func (s *integrationEnterpriseTestSuite) TestExternalIntegrationsTeamConfig() {
},
}, http.StatusUnprocessableEntity, &tmResp)
// updating the integration with invalid credentials fails
// updating to use the same project key fails (must be unique)
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), fleet.TeamPayload{
Integrations: &fleet.TeamIntegrations{
Jira: []*fleet.TeamJiraIntegration{
{
URL: srvURL,
Username: "fail",
APIToken: "foo",
ProjectKey: "qux",
EnableFailingPolicies: true,
},
{
URL: srvURL,
Username: "ok",
APIToken: "foo2",
ProjectKey: "qux2",
EnableFailingPolicies: false,
},
},
},
}, http.StatusBadRequest, &tmResp)
// updating the integration with invalid credentials fails even if the integration is disabled
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), fleet.TeamPayload{
Integrations: &fleet.TeamIntegrations{
Jira: []*fleet.TeamJiraIntegration{
{
URL: srvURL,
Username: "ok",
APIToken: "foo",
ProjectKey: "qux",
EnableFailingPolicies: true,
},
{
URL: srvURL,
Username: "fail",
APIToken: "foo2",
ProjectKey: "qux2",
EnableFailingPolicies: false,
},
},
},
}, http.StatusBadRequest, &tmResp)
// updating to use the same project key fails (must be unique per project key)
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), fleet.TeamPayload{
Integrations: &fleet.TeamIntegrations{
Jira: []*fleet.TeamJiraIntegration{
{
URL: srvURL,
Username: "ok",
APIToken: "foo",
ProjectKey: "qux",
EnableFailingPolicies: true,
},
{
URL: srvURL,
Username: "ok",
APIToken: "foo2",
ProjectKey: "qux",
EnableFailingPolicies: false,
},
@ -656,36 +627,12 @@ func (s *integrationEnterpriseTestSuite) TestExternalIntegrationsTeamConfig() {
},
}, http.StatusUnprocessableEntity, &tmResp)
// unknown project key fails
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), fleet.TeamPayload{
Integrations: &fleet.TeamIntegrations{
Jira: []*fleet.TeamJiraIntegration{
{
URL: srvURL,
Username: "ok",
APIToken: "foo",
ProjectKey: "qux",
EnableFailingPolicies: true,
},
{
URL: srvURL,
Username: "ok",
APIToken: "foo2",
ProjectKey: "nosuchproject",
EnableFailingPolicies: false,
},
},
},
}, http.StatusBadRequest, &tmResp)
// remove second integration, disable first so that nothing is enabled now
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), fleet.TeamPayload{
Integrations: &fleet.TeamIntegrations{
Jira: []*fleet.TeamJiraIntegration{
{
URL: srvURL,
Username: "ok",
APIToken: "foo",
ProjectKey: "qux",
EnableFailingPolicies: false,
},
@ -704,27 +651,68 @@ func (s *integrationEnterpriseTestSuite) TestExternalIntegrationsTeamConfig() {
// set environmental varible to use Zendesk test client
os.Setenv("TEST_ZENDESK_CLIENT", "true")
// add an unknown automation - does not exist at the global level
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), fleet.TeamPayload{Integrations: &fleet.TeamIntegrations{
Zendesk: []*fleet.TeamZendeskIntegration{
{
URL: srvURL,
GroupID: 122,
EnableFailingPolicies: false,
},
},
}}, http.StatusUnprocessableEntity, &tmResp)
// add a couple Zendesk integrations at the global level (122 and 123), keep the jira ones too
s.DoRaw("PATCH", "/api/v1/fleet/config", []byte(fmt.Sprintf(`{
"integrations": {
"zendesk": [
{
"url": %q,
"email": "a@b.c",
"api_token": "ok",
"group_id": 122
},
{
"url": %[1]q,
"email": "b@b.c",
"api_token": "ok",
"group_id": 123
}
],
"jira": [
{
"url": %[1]q,
"username": "ok",
"api_token": "foo",
"project_key": "qux"
},
{
"url": %[1]q,
"username": "ok",
"api_token": "foo",
"project_key": "qux2"
}
]
}
}`, srvURL)), http.StatusOK)
// enable a Zendesk automation - should fail as the webhook is enabled too
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), fleet.TeamPayload{Integrations: &fleet.TeamIntegrations{
Zendesk: []*fleet.TeamZendeskIntegration{
{
URL: srvURL,
Email: "a@b.c",
APIToken: "ok",
GroupID: 122,
EnableFailingPolicies: true,
},
},
}}, http.StatusUnprocessableEntity, &tmResp)
// disable the webhook and enable the automation, should work with valid user
// disable the webhook and enable the automation
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), fleet.TeamPayload{
Integrations: &fleet.TeamIntegrations{
Zendesk: []*fleet.TeamZendeskIntegration{
{
URL: srvURL,
Email: "a@b.c",
APIToken: "ok",
GroupID: 122,
EnableFailingPolicies: true,
},
@ -738,7 +726,7 @@ func (s *integrationEnterpriseTestSuite) TestExternalIntegrationsTeamConfig() {
},
}, http.StatusOK, &tmResp)
require.Len(t, tmResp.Team.Config.Integrations.Zendesk, 1)
require.Equal(t, fleet.MaskedPassword, tmResp.Team.Config.Integrations.Zendesk[0].APIToken)
require.Equal(t, int64(122), tmResp.Team.Config.Integrations.Zendesk[0].GroupID)
// enable the webhook without changing the integration should fail (an integration is already enabled)
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), fleet.TeamPayload{WebhookSettings: &fleet.TeamWebhookSettings{
@ -754,15 +742,11 @@ func (s *integrationEnterpriseTestSuite) TestExternalIntegrationsTeamConfig() {
Zendesk: []*fleet.TeamZendeskIntegration{
{
URL: srvURL,
Email: "a@b.c",
APIToken: "ok",
GroupID: 122,
EnableFailingPolicies: true,
},
{
URL: srvURL,
Email: "b@b.c",
APIToken: "ok",
GroupID: 123,
EnableFailingPolicies: false,
},
@ -770,8 +754,8 @@ func (s *integrationEnterpriseTestSuite) TestExternalIntegrationsTeamConfig() {
},
}, http.StatusOK, &tmResp)
require.Len(t, tmResp.Team.Config.Integrations.Zendesk, 2)
require.Equal(t, fleet.MaskedPassword, tmResp.Team.Config.Integrations.Zendesk[0].APIToken)
require.Equal(t, fleet.MaskedPassword, tmResp.Team.Config.Integrations.Zendesk[1].APIToken)
require.Equal(t, int64(122), tmResp.Team.Config.Integrations.Zendesk[0].GroupID)
require.Equal(t, int64(123), tmResp.Team.Config.Integrations.Zendesk[1].GroupID)
// enabling the second without disabling the first fails
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), fleet.TeamPayload{
@ -779,15 +763,11 @@ func (s *integrationEnterpriseTestSuite) TestExternalIntegrationsTeamConfig() {
Zendesk: []*fleet.TeamZendeskIntegration{
{
URL: srvURL,
Email: "a@b.c",
APIToken: "ok",
GroupID: 122,
EnableFailingPolicies: true,
},
{
URL: srvURL,
Email: "b@b.c",
APIToken: "ok",
GroupID: 123,
EnableFailingPolicies: true,
},
@ -795,65 +775,17 @@ func (s *integrationEnterpriseTestSuite) TestExternalIntegrationsTeamConfig() {
},
}, http.StatusUnprocessableEntity, &tmResp)
// updating the integration with invalid credentials fails
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), fleet.TeamPayload{
Integrations: &fleet.TeamIntegrations{
Zendesk: []*fleet.TeamZendeskIntegration{
{
URL: srvURL,
Email: "a@b.c",
APIToken: "fail",
GroupID: 122,
EnableFailingPolicies: true,
},
{
URL: srvURL,
Email: "b@b.c",
APIToken: "ok",
GroupID: 123,
EnableFailingPolicies: false,
},
},
},
}, http.StatusBadRequest, &tmResp)
// updating the integration with invalid credentials fails, even if the integration is disabled
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), fleet.TeamPayload{
Integrations: &fleet.TeamIntegrations{
Zendesk: []*fleet.TeamZendeskIntegration{
{
URL: srvURL,
Email: "a@b.c",
APIToken: "ok",
GroupID: 122,
EnableFailingPolicies: true,
},
{
URL: srvURL,
Email: "b@b.c",
APIToken: "fail",
GroupID: 123,
EnableFailingPolicies: false,
},
},
},
}, http.StatusBadRequest, &tmResp)
// updating to use the same group ID fails (must be unique per group ID)
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), fleet.TeamPayload{
Integrations: &fleet.TeamIntegrations{
Zendesk: []*fleet.TeamZendeskIntegration{
{
URL: srvURL,
Email: "a@b.c",
APIToken: "ok",
GroupID: 123,
EnableFailingPolicies: true,
},
{
URL: srvURL,
Email: "b@b.c",
APIToken: "ok",
GroupID: 123,
EnableFailingPolicies: false,
},
@ -861,36 +793,12 @@ func (s *integrationEnterpriseTestSuite) TestExternalIntegrationsTeamConfig() {
},
}, http.StatusUnprocessableEntity, &tmResp)
// unknown group ID fails
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), fleet.TeamPayload{
Integrations: &fleet.TeamIntegrations{
Zendesk: []*fleet.TeamZendeskIntegration{
{
URL: srvURL,
Email: "a@b.c",
APIToken: "ok",
GroupID: 122,
EnableFailingPolicies: true,
},
{
URL: srvURL,
Email: "b@b.c",
APIToken: "ok",
GroupID: 999,
EnableFailingPolicies: false,
},
},
},
}, http.StatusBadRequest, &tmResp)
// remove second Zendesk integration, add disabled Jira integration
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), fleet.TeamPayload{
Integrations: &fleet.TeamIntegrations{
Zendesk: []*fleet.TeamZendeskIntegration{
{
URL: srvURL,
Email: "a@b.c",
APIToken: "ok",
GroupID: 122,
EnableFailingPolicies: true,
},
@ -898,8 +806,6 @@ func (s *integrationEnterpriseTestSuite) TestExternalIntegrationsTeamConfig() {
Jira: []*fleet.TeamJiraIntegration{
{
URL: srvURL,
Username: "ok",
APIToken: "foo",
ProjectKey: "qux",
EnableFailingPolicies: false,
},
@ -907,9 +813,9 @@ func (s *integrationEnterpriseTestSuite) TestExternalIntegrationsTeamConfig() {
},
}, http.StatusOK, &tmResp)
require.Len(t, tmResp.Team.Config.Integrations.Jira, 1)
require.Equal(t, fleet.MaskedPassword, tmResp.Team.Config.Integrations.Jira[0].APIToken)
require.Equal(t, "qux", tmResp.Team.Config.Integrations.Jira[0].ProjectKey)
require.Len(t, tmResp.Team.Config.Integrations.Zendesk, 1)
require.Equal(t, fleet.MaskedPassword, tmResp.Team.Config.Integrations.Zendesk[0].APIToken)
require.Equal(t, int64(122), tmResp.Team.Config.Integrations.Zendesk[0].GroupID)
// enabling a Jira integration when a Zendesk one is enabled fails
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), fleet.TeamPayload{
@ -917,8 +823,6 @@ func (s *integrationEnterpriseTestSuite) TestExternalIntegrationsTeamConfig() {
Zendesk: []*fleet.TeamZendeskIntegration{
{
URL: srvURL,
Email: "a@b.c",
APIToken: "ok",
GroupID: 122,
EnableFailingPolicies: true,
},
@ -926,8 +830,6 @@ func (s *integrationEnterpriseTestSuite) TestExternalIntegrationsTeamConfig() {
Jira: []*fleet.TeamJiraIntegration{
{
URL: srvURL,
Username: "ok",
APIToken: "foo",
ProjectKey: "qux",
EnableFailingPolicies: true,
},
@ -935,6 +837,140 @@ func (s *integrationEnterpriseTestSuite) TestExternalIntegrationsTeamConfig() {
},
}, http.StatusUnprocessableEntity, &tmResp)
// set additional integrations on the team
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), fleet.TeamPayload{
Integrations: &fleet.TeamIntegrations{
Zendesk: []*fleet.TeamZendeskIntegration{
{
URL: srvURL,
GroupID: 122,
EnableFailingPolicies: true,
},
{
URL: srvURL,
GroupID: 123,
EnableFailingPolicies: false,
},
},
Jira: []*fleet.TeamJiraIntegration{
{
URL: srvURL,
ProjectKey: "qux",
EnableFailingPolicies: false,
},
},
},
}, http.StatusOK, &tmResp)
// removing Zendesk 122 from the global config removes it from the team too
s.DoRaw("PATCH", "/api/v1/fleet/config", []byte(fmt.Sprintf(`{
"integrations": {
"zendesk": [
{
"url": %[1]q,
"email": "b@b.c",
"api_token": "ok",
"group_id": 123
}
],
"jira": [
{
"url": %[1]q,
"username": "ok",
"api_token": "foo",
"project_key": "qux"
},
{
"url": %[1]q,
"username": "ok",
"api_token": "foo",
"project_key": "qux2"
}
]
}
}`, srvURL)), http.StatusOK)
// get the team, only one Zendesk integration remains, none are enabled
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), nil, http.StatusOK, &getResp)
require.Len(t, getResp.Team.Config.Integrations.Jira, 1)
require.Equal(t, "qux", getResp.Team.Config.Integrations.Jira[0].ProjectKey)
require.False(t, getResp.Team.Config.Integrations.Jira[0].EnableFailingPolicies)
require.Len(t, getResp.Team.Config.Integrations.Zendesk, 1)
require.Equal(t, int64(123), getResp.Team.Config.Integrations.Zendesk[0].GroupID)
require.False(t, getResp.Team.Config.Integrations.Zendesk[0].EnableFailingPolicies)
// removing Jira qux2 from the global config does not impact the team as it is unused.
s.DoRaw("PATCH", "/api/v1/fleet/config", []byte(fmt.Sprintf(`{
"integrations": {
"zendesk": [
{
"url": %[1]q,
"email": "b@b.c",
"api_token": "ok",
"group_id": 123
}
],
"jira": [
{
"url": %[1]q,
"username": "ok",
"api_token": "foo",
"project_key": "qux"
}
]
}
}`, srvURL)), http.StatusOK)
// get the team, integrations are unchanged
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), nil, http.StatusOK, &getResp)
require.Len(t, getResp.Team.Config.Integrations.Jira, 1)
require.Equal(t, "qux", getResp.Team.Config.Integrations.Jira[0].ProjectKey)
require.False(t, getResp.Team.Config.Integrations.Jira[0].EnableFailingPolicies)
require.Len(t, getResp.Team.Config.Integrations.Zendesk, 1)
require.Equal(t, int64(123), getResp.Team.Config.Integrations.Zendesk[0].GroupID)
require.False(t, getResp.Team.Config.Integrations.Zendesk[0].EnableFailingPolicies)
// enable Jira qux for the team
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), fleet.TeamPayload{
Integrations: &fleet.TeamIntegrations{
Zendesk: []*fleet.TeamZendeskIntegration{
{
URL: srvURL,
GroupID: 123,
EnableFailingPolicies: false,
},
},
Jira: []*fleet.TeamJiraIntegration{
{
URL: srvURL,
ProjectKey: "qux",
EnableFailingPolicies: true,
},
},
},
}, http.StatusOK, &tmResp)
// removing Zendesk 123 from the global config removes it from the team but
// leaves the Jira integration enabled.
s.DoRaw("PATCH", "/api/v1/fleet/config", []byte(fmt.Sprintf(`{
"integrations": {
"jira": [
{
"url": %[1]q,
"username": "ok",
"api_token": "foo",
"project_key": "qux"
}
]
}
}`, srvURL)), http.StatusOK)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), nil, http.StatusOK, &getResp)
require.Len(t, getResp.Team.Config.Integrations.Jira, 1)
require.Equal(t, "qux", getResp.Team.Config.Integrations.Jira[0].ProjectKey)
require.True(t, getResp.Team.Config.Integrations.Jira[0].EnableFailingPolicies)
require.Len(t, getResp.Team.Config.Integrations.Zendesk, 0)
// remove all integrations on exit, so that other tests can enable the
// webhook as needed
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), fleet.TeamPayload{
@ -948,6 +984,10 @@ func (s *integrationEnterpriseTestSuite) TestExternalIntegrationsTeamConfig() {
require.Len(t, tmResp.Team.Config.Integrations.Zendesk, 0)
require.False(t, tmResp.Team.Config.WebhookSettings.FailingPoliciesWebhook.Enable)
require.Empty(t, tmResp.Team.Config.WebhookSettings.FailingPoliciesWebhook.DestinationURL)
s.DoRaw("PATCH", "/api/v1/fleet/config", []byte(`{
"integrations": {}
}`), http.StatusOK)
}
func (s *integrationEnterpriseTestSuite) TestListDevicePolicies() {

View File

@ -31,13 +31,6 @@ func listTeamsEndpoint(ctx context.Context, request interface{}, svc fleet.Servi
resp := listTeamsResponse{Teams: []fleet.Team{}}
for _, team := range teams {
// mask the API token of integrations
for _, intg := range team.Config.Integrations.Jira {
intg.APIToken = fleet.MaskedPassword
}
for _, intg := range team.Config.Integrations.Zendesk {
intg.APIToken = fleet.MaskedPassword
}
resp.Teams = append(resp.Teams, *team)
}
return resp, nil
@ -72,14 +65,6 @@ func getTeamEndpoint(ctx context.Context, request interface{}, svc fleet.Service
if err != nil {
return getTeamResponse{Err: err}, nil
}
// mask the API token of integrations
for _, intg := range team.Config.Integrations.Jira {
intg.APIToken = fleet.MaskedPassword
}
for _, intg := range team.Config.Integrations.Zendesk {
intg.APIToken = fleet.MaskedPassword
}
return getTeamResponse{Team: team}, nil
}
@ -113,14 +98,6 @@ func createTeamEndpoint(ctx context.Context, request interface{}, svc fleet.Serv
if err != nil {
return teamResponse{Err: err}, nil
}
// mask the API token of integrations
for _, intg := range team.Config.Integrations.Jira {
intg.APIToken = fleet.MaskedPassword
}
for _, intg := range team.Config.Integrations.Zendesk {
intg.APIToken = fleet.MaskedPassword
}
return teamResponse{Team: team}, nil
}
@ -147,14 +124,6 @@ func modifyTeamEndpoint(ctx context.Context, request interface{}, svc fleet.Serv
if err != nil {
return teamResponse{Err: err}, nil
}
// mask the API token of integrations
for _, intg := range team.Config.Integrations.Jira {
intg.APIToken = fleet.MaskedPassword
}
for _, intg := range team.Config.Integrations.Zendesk {
intg.APIToken = fleet.MaskedPassword
}
return teamResponse{Team: team}, err
}
@ -243,14 +212,6 @@ func modifyTeamAgentOptionsEndpoint(ctx context.Context, request interface{}, sv
if err != nil {
return teamResponse{Err: err}, nil
}
// mask the API token of integrations
for _, intg := range team.Config.Integrations.Jira {
intg.APIToken = fleet.MaskedPassword
}
for _, intg := range team.Config.Integrations.Zendesk {
intg.APIToken = fleet.MaskedPassword
}
return teamResponse{Team: team}, err
}
@ -311,14 +272,6 @@ func addTeamUsersEndpoint(ctx context.Context, request interface{}, svc fleet.Se
if err != nil {
return teamResponse{Err: err}, nil
}
// mask the API token of integrations
for _, intg := range team.Config.Integrations.Jira {
intg.APIToken = fleet.MaskedPassword
}
for _, intg := range team.Config.Integrations.Zendesk {
intg.APIToken = fleet.MaskedPassword
}
return teamResponse{Team: team}, err
}
@ -336,14 +289,6 @@ func deleteTeamUsersEndpoint(ctx context.Context, request interface{}, svc fleet
if err != nil {
return teamResponse{Err: err}, nil
}
// mask the API token of integrations
for _, intg := range team.Config.Integrations.Jira {
intg.APIToken = fleet.MaskedPassword
}
for _, intg := range team.Config.Integrations.Zendesk {
intg.APIToken = fleet.MaskedPassword
}
return teamResponse{Team: team}, err
}

View File

@ -126,6 +126,11 @@ func (j *Jira) getClient(ctx context.Context, args jiraArgs) (JiraClient, error)
key += fmt.Sprint(teamID)
}
ac, err := j.Datastore.AppConfig(ctx)
if err != nil {
return nil, err
}
// load the config that would be used to create the client first - it is
// needed to check if an existing client is configured the same or if its
// configuration has changed since it was created.
@ -136,7 +141,11 @@ func (j *Jira) getClient(ctx context.Context, args jiraArgs) (JiraClient, error)
return nil, err
}
for _, intg := range tm.Config.Integrations.Jira {
intgs, err := tm.Config.Integrations.MatchWithIntegrations(ac.Integrations)
if err != nil {
return nil, err
}
for _, intg := range intgs.Jira {
if intgType == intgTypeFailingPolicy && intg.EnableFailingPolicies {
opts = &externalsvc.JiraOptions{
BaseURL: intg.URL,
@ -148,10 +157,6 @@ func (j *Jira) getClient(ctx context.Context, args jiraArgs) (JiraClient, error)
}
}
} else {
ac, err := j.Datastore.AppConfig(ctx)
if err != nil {
return nil, err
}
for _, intg := range ac.Integrations.Jira {
if (intgType == intgTypeVuln && intg.EnableSoftwareVulnerabilities) ||
(intgType == intgTypeFailingPolicy && intg.EnableFailingPolicies) {

View File

@ -32,7 +32,7 @@ func TestJiraRun(t *testing.T) {
ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
return &fleet.AppConfig{Integrations: fleet.Integrations{
Jira: []*fleet.JiraIntegration{
{EnableSoftwareVulnerabilities: true, TeamJiraIntegration: fleet.TeamJiraIntegration{EnableFailingPolicies: true}},
{EnableSoftwareVulnerabilities: true, EnableFailingPolicies: true},
},
}}, nil
}
@ -215,7 +215,10 @@ func TestJiraRunClientUpdate(t *testing.T) {
globalCount++
return &fleet.AppConfig{Integrations: fleet.Integrations{
Jira: []*fleet.JiraIntegration{
{TeamJiraIntegration: fleet.TeamJiraIntegration{ProjectKey: "0", EnableFailingPolicies: true}},
{ProjectKey: "0", EnableFailingPolicies: true},
{ProjectKey: "1", EnableFailingPolicies: false}, // the team integration will use the project keys 1-3
{ProjectKey: "2", EnableFailingPolicies: false},
{ProjectKey: "3", EnableFailingPolicies: false},
},
}}, nil
}
@ -290,6 +293,6 @@ func TestJiraRunClientUpdate(t *testing.T) {
// it should've created 3 clients - the global one, and the first 2 calls with team 123
require.Equal(t, []string{"0", "1", "2"}, projectKeys)
require.Equal(t, 2, globalCount)
require.Equal(t, 5, globalCount) // app config is requested every time
require.Equal(t, 3, teamCount)
}

View File

@ -120,6 +120,11 @@ func (z *Zendesk) getClient(ctx context.Context, args zendeskArgs) (ZendeskClien
key += fmt.Sprint(teamID)
}
ac, err := z.Datastore.AppConfig(ctx)
if err != nil {
return nil, err
}
// load the config that would be used to create the client first - it is
// needed to check if an existing client is configured the same or if its
// configuration has changed since it was created.
@ -130,7 +135,12 @@ func (z *Zendesk) getClient(ctx context.Context, args zendeskArgs) (ZendeskClien
return nil, err
}
for _, intg := range tm.Config.Integrations.Zendesk {
intgs, err := tm.Config.Integrations.MatchWithIntegrations(ac.Integrations)
if err != nil {
return nil, err
}
for _, intg := range intgs.Zendesk {
if intgType == intgTypeFailingPolicy && intg.EnableFailingPolicies {
opts = &externalsvc.ZendeskOptions{
URL: intg.URL,
@ -142,10 +152,6 @@ func (z *Zendesk) getClient(ctx context.Context, args zendeskArgs) (ZendeskClien
}
}
} else {
ac, err := z.Datastore.AppConfig(ctx)
if err != nil {
return nil, err
}
for _, intg := range ac.Integrations.Zendesk {
if (intgType == intgTypeVuln && intg.EnableSoftwareVulnerabilities) ||
(intgType == intgTypeFailingPolicy && intg.EnableFailingPolicies) {

View File

@ -32,7 +32,7 @@ func TestZendeskRun(t *testing.T) {
ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
return &fleet.AppConfig{Integrations: fleet.Integrations{
Zendesk: []*fleet.ZendeskIntegration{
{EnableSoftwareVulnerabilities: true, TeamZendeskIntegration: fleet.TeamZendeskIntegration{EnableFailingPolicies: true}},
{EnableSoftwareVulnerabilities: true, EnableFailingPolicies: true},
},
}}, nil
}
@ -203,7 +203,10 @@ func TestZendeskRunClientUpdate(t *testing.T) {
globalCount++
return &fleet.AppConfig{Integrations: fleet.Integrations{
Zendesk: []*fleet.ZendeskIntegration{
{TeamZendeskIntegration: fleet.TeamZendeskIntegration{GroupID: 0, EnableFailingPolicies: true}},
{GroupID: 0, EnableFailingPolicies: true},
{GroupID: 1, EnableFailingPolicies: false}, // the team integration will use the group IDs 1-3
{GroupID: 2, EnableFailingPolicies: false},
{GroupID: 3, EnableFailingPolicies: false},
},
}}, nil
}
@ -278,6 +281,6 @@ func TestZendeskRunClientUpdate(t *testing.T) {
// it should've created 3 clients - the global one, and the first 2 calls with team 123
require.Equal(t, []int64{0, 1, 2}, groupIDs)
require.Equal(t, 2, globalCount)
require.Equal(t, 5, globalCount) // app config is requested every time
require.Equal(t, 3, teamCount)
}

View File

@ -82,13 +82,11 @@ func main() {
Jira: []*fleet.JiraIntegration{
{
EnableSoftwareVulnerabilities: *cve != "",
TeamJiraIntegration: fleet.TeamJiraIntegration{
URL: *jiraURL,
Username: *jiraUsername,
APIToken: jiraPassword,
ProjectKey: *jiraProjectKey,
EnableFailingPolicies: *failingPolicyID > 0,
},
URL: *jiraURL,
Username: *jiraUsername,
APIToken: jiraPassword,
ProjectKey: *jiraProjectKey,
EnableFailingPolicies: *failingPolicyID > 0,
},
},
},
@ -103,8 +101,6 @@ func main() {
Jira: []*fleet.TeamJiraIntegration{
{
URL: *jiraURL,
Username: *jiraUsername,
APIToken: jiraPassword,
ProjectKey: *jiraProjectKey,
EnableFailingPolicies: *failingPolicyID > 0,
},

View File

@ -82,13 +82,11 @@ func main() {
Zendesk: []*fleet.ZendeskIntegration{
{
EnableSoftwareVulnerabilities: *cve != "",
TeamZendeskIntegration: fleet.TeamZendeskIntegration{
URL: *zendeskURL,
Email: *zendeskEmail,
APIToken: zendeskToken,
GroupID: *zendeskGroupID,
EnableFailingPolicies: *failingPolicyID > 0,
},
URL: *zendeskURL,
Email: *zendeskEmail,
APIToken: zendeskToken,
GroupID: *zendeskGroupID,
EnableFailingPolicies: *failingPolicyID > 0,
},
},
},
@ -103,8 +101,6 @@ func main() {
Zendesk: []*fleet.TeamZendeskIntegration{
{
URL: *zendeskURL,
Email: *zendeskEmail,
APIToken: zendeskToken,
GroupID: *zendeskGroupID,
EnableFailingPolicies: *failingPolicyID > 0,
},