github.com/Axway/agent-sdk@v1.1.101/pkg/migrate/attributemigration.go (about)

     1  package migrate
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"regexp"
     7  	"sync"
     8  
     9  	apiv1 "github.com/Axway/agent-sdk/pkg/apic/apiserver/models/api/v1"
    10  	management "github.com/Axway/agent-sdk/pkg/apic/apiserver/models/management/v1alpha1"
    11  	defs "github.com/Axway/agent-sdk/pkg/apic/definitions"
    12  	"github.com/Axway/agent-sdk/pkg/config"
    13  	"github.com/Axway/agent-sdk/pkg/util"
    14  	"github.com/Axway/agent-sdk/pkg/util/log"
    15  )
    16  
    17  var oldAttrs = []string{
    18  	defs.AttrPreviousAPIServiceRevisionID,
    19  	defs.AttrExternalAPIID,
    20  	defs.AttrExternalAPIPrimaryKey,
    21  	defs.AttrExternalAPIName,
    22  	defs.AttrExternalAPIStage,
    23  	defs.AttrCreatedBy,
    24  }
    25  
    26  var regexes = make([]string, 0)
    27  
    28  var tagRegexes = make([]string, 0)
    29  
    30  // MatchAttrPattern matches attribute patterns to match against an attribute to migrate to the x-agent-details subresource
    31  func MatchAttrPattern(pattern ...string) {
    32  	regexes = append(regexes, pattern...)
    33  }
    34  
    35  // MatchAttr matches attributes to migrate to the x-agent-details subresource
    36  func MatchAttr(attr ...string) {
    37  	oldAttrs = append(oldAttrs, attr...)
    38  }
    39  
    40  // RemoveTagPattern matches tags by a pattern for removal from the resource
    41  func RemoveTagPattern(tags ...string) {
    42  	tagRegexes = append(tagRegexes, tags...)
    43  }
    44  
    45  type client interface {
    46  	ExecuteAPI(method, url string, queryParam map[string]string, buffer []byte) ([]byte, error)
    47  	GetAPIV1ResourceInstances(query map[string]string, URL string) ([]*apiv1.ResourceInstance, error)
    48  	UpdateResourceInstance(ri apiv1.Interface) (*apiv1.ResourceInstance, error)
    49  	CreateOrUpdateResource(data apiv1.Interface) (*apiv1.ResourceInstance, error)
    50  	CreateSubResource(rm apiv1.ResourceMeta, subs map[string]interface{}) error
    51  	DeleteResourceInstance(ri apiv1.Interface) error
    52  	GetResource(url string) (*apiv1.ResourceInstance, error)
    53  }
    54  
    55  type item struct {
    56  	ri     *apiv1.ResourceInstance
    57  	update bool
    58  }
    59  
    60  type migrateFunc func(ri *apiv1.ResourceInstance) error
    61  
    62  // AttributeMigration - used for migrating attributes to subresource
    63  type AttributeMigration struct {
    64  	migration
    65  	riMutex sync.Mutex
    66  }
    67  
    68  // NewAttributeMigration creates a new AttributeMigration
    69  func NewAttributeMigration(client client, cfg config.CentralConfig) *AttributeMigration {
    70  	return &AttributeMigration{
    71  		migration: migration{
    72  			client: client,
    73  			cfg:    cfg,
    74  		},
    75  		riMutex: sync.Mutex{},
    76  	}
    77  }
    78  
    79  // Migrate - receives an APIService as a ResourceInstance, and checks if an attribute migration should be performed.
    80  // If a migration should occur, then the APIService, Instances, and Revisions, that refer to the APIService will all have their attributes updated.
    81  func (m *AttributeMigration) Migrate(_ context.Context, ri *apiv1.ResourceInstance) (*apiv1.ResourceInstance, error) {
    82  	if ri.Kind != management.APIServiceGVK().Kind {
    83  		return ri, nil
    84  	}
    85  
    86  	// skip migration if x-agent-details is found for the service.
    87  	details := util.GetAgentDetails(ri)
    88  	if len(details) > 0 {
    89  		return ri, nil
    90  	}
    91  
    92  	log.Debugf("migrating attributes for service: %s", ri.Name)
    93  
    94  	funcs := []migrateFunc{
    95  		m.updateSvc,
    96  		m.updateInst,
    97  	}
    98  
    99  	errCh := make(chan error, len(funcs))
   100  	wg := &sync.WaitGroup{}
   101  
   102  	for _, f := range funcs {
   103  		wg.Add(1)
   104  
   105  		go func(fun migrateFunc) {
   106  			defer wg.Done()
   107  
   108  			err := fun(ri)
   109  			errCh <- err
   110  		}(f)
   111  	}
   112  
   113  	wg.Wait()
   114  	close(errCh)
   115  
   116  	for e := range errCh {
   117  		if e != nil {
   118  			return ri, e
   119  		}
   120  	}
   121  
   122  	log.Debugf("finished migrating attributes for service: %s", ri.Name)
   123  
   124  	return ri, nil
   125  }
   126  
   127  // updateSvc updates the attributes on service in place, then updates on api server.
   128  func (m *AttributeMigration) updateSvc(ri *apiv1.ResourceInstance) error {
   129  	url := fmt.Sprintf("%s/%s", m.cfg.GetServicesURL(), ri.Name)
   130  	r, err := m.getRI(url)
   131  	if err != nil {
   132  		return err
   133  	}
   134  	item := updateAttrs(r)
   135  	if !item.update {
   136  		return nil
   137  	}
   138  
   139  	// replace the address value so that the Migrate func can return the updated resource instance
   140  	m.riMutex.Lock()
   141  	defer m.riMutex.Unlock()
   142  	*ri = *item.ri
   143  
   144  	return m.updateRI(item.ri)
   145  }
   146  
   147  // updateInst gets a list of instances for the service and updates their attributes.
   148  func (m *AttributeMigration) updateInst(ri *apiv1.ResourceInstance) error {
   149  	m.riMutex.Lock()
   150  	defer m.riMutex.Unlock()
   151  
   152  	q := map[string]string{
   153  		"query": queryFuncByMetadataID(ri.Metadata.ID),
   154  	}
   155  	url := m.cfg.GetInstancesURL()
   156  	if err := m.migrate(url, q); err != nil {
   157  		return err
   158  	}
   159  
   160  	return nil
   161  }
   162  
   163  func (m *AttributeMigration) migrate(resourceURL string, query map[string]string) error {
   164  	resources, err := m.client.GetAPIV1ResourceInstances(query, resourceURL)
   165  	if err != nil {
   166  		return err
   167  	}
   168  
   169  	items := make([]item, 0)
   170  
   171  	for _, ri := range resources {
   172  		item := updateAttrs(ri)
   173  		items = append(items, item)
   174  	}
   175  
   176  	wg := &sync.WaitGroup{}
   177  	errCh := make(chan error, len(items))
   178  
   179  	for _, item := range items {
   180  		if !item.update {
   181  			continue
   182  		}
   183  
   184  		wg.Add(1)
   185  
   186  		go func(ri *apiv1.ResourceInstance) {
   187  			defer wg.Done()
   188  
   189  			err := m.updateRI(ri)
   190  			errCh <- err
   191  		}(item.ri)
   192  	}
   193  
   194  	wg.Wait()
   195  	close(errCh)
   196  
   197  	for e := range errCh {
   198  		if e != nil {
   199  			return e
   200  		}
   201  	}
   202  
   203  	return nil
   204  }
   205  
   206  func updateAttrs(ri *apiv1.ResourceInstance) item {
   207  	details := util.GetAgentDetails(ri)
   208  	if details == nil {
   209  		details = make(map[string]interface{})
   210  	}
   211  
   212  	item := item{
   213  		ri:     ri,
   214  		update: false,
   215  	}
   216  
   217  	for _, attr := range oldAttrs {
   218  		if _, ok := ri.Attributes[attr]; ok {
   219  			details[attr] = ri.Attributes[attr]
   220  			delete(ri.Attributes, attr)
   221  			item.update = true
   222  		}
   223  	}
   224  
   225  	for _, reg := range regexes {
   226  		for attr := range ri.Attributes {
   227  			if ok, _ := regexp.MatchString(reg, attr); ok {
   228  				details[attr] = ri.Attributes[attr]
   229  				delete(ri.Attributes, attr)
   230  				item.update = true
   231  			}
   232  		}
   233  	}
   234  
   235  	var tags []string
   236  
   237  	for _, tag := range ri.Tags {
   238  		for _, reg := range tagRegexes {
   239  			if ok, _ := regexp.MatchString(reg, tag); ok {
   240  				item.update = true
   241  			} else {
   242  				tags = append(tags, tag)
   243  			}
   244  		}
   245  	}
   246  
   247  	ri.Tags = tags
   248  
   249  	util.SetAgentDetails(ri, details)
   250  
   251  	return item
   252  }