Skip to content

Commit

Permalink
add generic ReconcileTags member function to client (#422)
Browse files Browse the repository at this point in the history
* add generic ReconcileTags member function to client

* add generic ReconcileAliases member function to client

* add changie log

* update ReconcileTags
  • Loading branch information
davidbloss authored Jun 18, 2024
1 parent 8016ef2 commit 14e5a66
Show file tree
Hide file tree
Showing 10 changed files with 161 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .changes/unreleased/Feature-20240617-115124.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
kind: Feature
body: add generic ReconcileTags member function to client
time: 2024-06-17T11:51:24.657537-05:00
3 changes: 3 additions & 0 deletions .changes/unreleased/Feature-20240618-085940.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
kind: Feature
body: add generic ReconcileAliases member function to client
time: 2024-06-18T08:59:40.275051-05:00
80 changes: 80 additions & 0 deletions aliases.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@ package opslevel

import (
"fmt"
"slices"
"strings"
)

type AliasOwnerInterface interface {
AliasOwnerType() AliasOwnerTypeEnum
}

func (client *Client) CreateAliases(ownerId ID, aliases []string) ([]string, error) {
var output []string
var errors []string
Expand Down Expand Up @@ -44,20 +49,41 @@ func (client *Client) CreateAlias(input AliasCreateInput) ([]string, error) {
return output, HandleErrors(err, m.Payload.Errors)
}

func (client *Client) DeleteDomainAlias(alias string) error {
return client.DeleteAlias(AliasDeleteInput{
Alias: alias,
OwnerType: AliasOwnerTypeEnumDomain,
})
}

func (client *Client) DeleteInfraAlias(alias string) error {
return client.DeleteAlias(AliasDeleteInput{
Alias: alias,
OwnerType: AliasOwnerTypeEnumInfrastructureResource,
})
}

func (client *Client) DeleteScorecardAlias(alias string) error {
return client.DeleteAlias(AliasDeleteInput{
Alias: alias,
OwnerType: AliasOwnerTypeEnumScorecard,
})
}

func (client *Client) DeleteServiceAlias(alias string) error {
return client.DeleteAlias(AliasDeleteInput{
Alias: alias,
OwnerType: AliasOwnerTypeEnumService,
})
}

func (client *Client) DeleteSystemAlias(alias string) error {
return client.DeleteAlias(AliasDeleteInput{
Alias: alias,
OwnerType: AliasOwnerTypeEnumSystem,
})
}

func (client *Client) DeleteTeamAlias(alias string) error {
return client.DeleteAlias(AliasDeleteInput{
Alias: alias,
Expand All @@ -78,3 +104,57 @@ func (client *Client) DeleteAlias(input AliasDeleteInput) error {
err := client.Mutate(&m, v, WithName("AliasDelete"))
return HandleErrors(err, m.Payload.Errors)
}

// ReconcileAliases manages aliases API operations for AliasOwnerInterface implementations
//
// Aliases not in 'aliasesWanted' will be deleted, new tags from 'aliasesWanted' will be created. Reconciled aliases are returned.
func (client *Client) ReconcileAliases(resourceType AliasOwnerInterface, aliasesWanted []string) ([]string, error) {
var deleteAliasFunc func(string) error
var existingAliases []string
var resourceId ID

switch resource := resourceType.(type) {
case *Domain:
deleteAliasFunc = client.DeleteDomainAlias
existingAliases = resource.ManagedAliases
resourceId = resource.Id
case *InfrastructureResource:
deleteAliasFunc = client.DeleteInfraAlias
existingAliases = resource.Aliases
resourceId = ID(resource.Id)
case *Scorecard:
deleteAliasFunc = client.DeleteScorecardAlias
existingAliases = resource.Aliases
resourceId = resource.Id
case *Service:
deleteAliasFunc = client.DeleteServiceAlias
existingAliases = resource.ManagedAliases
resourceId = resource.Id
case *System:
deleteAliasFunc = client.DeleteSystemAlias
existingAliases = resource.Aliases
resourceId = resource.Id
case *Team:
deleteAliasFunc = client.DeleteTeamAlias
existingAliases = resource.ManagedAliases
resourceId = resource.Id
}

// delete aliases found in resource but not listed in aliasesWanted
for _, alias := range existingAliases {
if !slices.Contains(aliasesWanted, alias) {
if err := deleteAliasFunc(alias); err != nil {
return []string{}, err
}
}
}

newServiceAliases := []string{}
for _, aliasWanted := range aliasesWanted {
if !slices.Contains(existingAliases, aliasWanted) {
newServiceAliases = append(newServiceAliases, aliasWanted)
}
}

return client.CreateAliases(resourceId, newServiceAliases)
}
4 changes: 4 additions & 0 deletions domain.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ type DomainConnection struct {
TotalCount int `json:"totalCount" graphql:"-"`
}

func (d *Domain) AliasOwnerType() AliasOwnerTypeEnum {
return AliasOwnerTypeEnumDomain
}

func (domainId *DomainId) GetTags(client *Client, variables *PayloadVariables) (*TagConnection, error) {
var q struct {
Account struct {
Expand Down
4 changes: 4 additions & 0 deletions infra.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ type InfraInput struct {
Data *JSON `json:"data" yaml:"data" default:"{\"name\":\"my-big-query\",\"engine\":\"BigQuery\",\"endpoint\":\"https://google.com\",\"replica\":false}"`
}

func (infrastructureResource *InfrastructureResource) AliasOwnerType() AliasOwnerTypeEnum {
return AliasOwnerTypeEnumInfrastructureResource
}

func (infrastructureResource *InfrastructureResource) GetTags(client *Client, variables *PayloadVariables) (*TagConnection, error) {
var q struct {
Account struct {
Expand Down
4 changes: 4 additions & 0 deletions scorecards.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ type ScorecardConnection struct {
TotalCount int `graphql:"totalCount"`
}

func (scorecard *Scorecard) AliasOwnerType() AliasOwnerTypeEnum {
return AliasOwnerTypeEnumScorecard
}

type ScorecardCategoryConnection struct {
Nodes []Category `graphql:"nodes"`
PageInfo PageInfo `graphql:"pageInfo"`
Expand Down
4 changes: 4 additions & 0 deletions service.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ type ServiceDocumentsConnection struct {
TotalCount int
}

func (s *Service) AliasOwnerType() AliasOwnerTypeEnum {
return AliasOwnerTypeEnumService
}

func (service *Service) ResourceId() ID {
return service.Id
}
Expand Down
4 changes: 4 additions & 0 deletions system.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ type SystemConnection struct {
TotalCount int `json:"totalCount" graphql:"-"`
}

func (system *System) AliasOwnerType() AliasOwnerTypeEnum {
return AliasOwnerTypeEnumSystem
}

func (systemId *SystemId) GetTags(client *Client, variables *PayloadVariables) (*TagConnection, error) {
var q struct {
Account struct {
Expand Down
51 changes: 51 additions & 0 deletions tags.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"errors"
"fmt"
"regexp"
"slices"
"strings"
)

type TagOwner string
Expand All @@ -30,6 +32,10 @@ type Tag struct {
Value string `json:"value"`
}

func (t Tag) Flatten() string {
return fmt.Sprintf("%s:%s", t.Key, t.Value)
}

type TagConnection struct {
Nodes []Tag
PageInfo PageInfo
Expand Down Expand Up @@ -200,3 +206,48 @@ func (client *Client) DeleteTag(id ID) error {
err := client.Mutate(&m, v, WithName("TagDelete"))
return HandleErrors(err, m.Payload.Errors)
}

// ReconcileTags manages tags API operations for TaggableResourceInterface implementations
//
// Tags not in 'tagsWanted' will be deleted, new tags from 'tagsWanted' will be created. Reconciled tags are returned.
func (client *Client) ReconcileTags(resourceType TaggableResourceInterface, tagsWanted []string) ([]Tag, error) {
var err error
var tagConnection *TagConnection
var assignedTags []Tag

tagConnection, err = resourceType.GetTags(client, nil)
if err != nil {
return assignedTags, err
}
if tagConnection == nil {
return assignedTags, fmt.Errorf("no tags found on %s with id '%s'", string(resourceType.ResourceType()), resourceType.ResourceId())
}

// delete tags found in resource but not listed in tagsWanted
for _, tag := range tagConnection.Nodes {
if !slices.Contains(tagsWanted, tag.Flatten()) {
if err := client.DeleteTag(tag.Id); err != nil {
return assignedTags, err
}
}
}

// format tags listed in Terraform config but not found in service
tagInput := map[string]string{}
for _, tag := range tagsWanted {
parts := strings.Split(tag, ":")
if len(parts) != 2 {
return assignedTags, fmt.Errorf("[%s] invalid tag, should be in format 'key:value' (only a single colon between the key and value, no spaces or special characters)", tag)
}
key := parts[0]
value := parts[1]
tagInput[key] = value
}
// assign tags listed in Terraform config but not found in service
assignedTags, err = client.AssignTags(string(resourceType.ResourceId()), tagInput)
if err != nil {
return assignedTags, err
}

return assignedTags, nil
}
4 changes: 4 additions & 0 deletions team.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,10 @@ func CreateContactWeb(address string, name *string) ContactInput {
}
}

func (team *Team) AliasOwnerType() AliasOwnerTypeEnum {
return AliasOwnerTypeEnumTeam
}

func (team *Team) HasTag(key string, value string) bool {
for _, tag := range team.Tags.Nodes {
if tag.Key == key && tag.Value == value {
Expand Down

0 comments on commit 14e5a66

Please sign in to comment.