github.com/jfrog/jfrog-cli-core/v2@v2.51.0/utils/usage/usage.go (about)

     1  package usage
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/jfrog/jfrog-cli-core/v2/artifactory/utils"
     7  	"github.com/jfrog/jfrog-cli-core/v2/utils/config"
     8  	"github.com/jfrog/jfrog-cli-core/v2/utils/coreutils"
     9  	xrayutils "github.com/jfrog/jfrog-cli-core/v2/utils/xray"
    10  	"github.com/jfrog/jfrog-client-go/artifactory/usage"
    11  	clientutils "github.com/jfrog/jfrog-client-go/utils"
    12  	"github.com/jfrog/jfrog-client-go/utils/errorutils"
    13  	"github.com/jfrog/jfrog-client-go/utils/log"
    14  	ecosysusage "github.com/jfrog/jfrog-client-go/utils/usage"
    15  	xrayusage "github.com/jfrog/jfrog-client-go/xray/usage"
    16  
    17  	"golang.org/x/sync/errgroup"
    18  )
    19  
    20  const (
    21  	ReportUsagePrefix     = "Usage Report:"
    22  	clientIdAttributeName = "clientId"
    23  )
    24  
    25  type UsageReporter struct {
    26  	ProductId       string
    27  	serverDetails   *config.ServerDetails
    28  	reportWaitGroup *errgroup.Group
    29  
    30  	sendToEcosystem   bool
    31  	sendToXray        bool
    32  	sendToArtifactory bool
    33  }
    34  
    35  type ReportFeature struct {
    36  	FeatureId  string
    37  	ClientId   string
    38  	Attributes []ReportUsageAttribute
    39  }
    40  
    41  type ReportUsageAttribute struct {
    42  	AttributeName  string
    43  	AttributeValue string
    44  }
    45  
    46  func NewUsageReporter(productId string, serverDetails *config.ServerDetails) *UsageReporter {
    47  	return &UsageReporter{
    48  		ProductId:         productId,
    49  		serverDetails:     serverDetails,
    50  		reportWaitGroup:   new(errgroup.Group),
    51  		sendToEcosystem:   true,
    52  		sendToXray:        true,
    53  		sendToArtifactory: true,
    54  	}
    55  }
    56  
    57  func ShouldReportUsage() (reportUsage bool) {
    58  	reportUsage, err := clientutils.GetBoolEnvValue(coreutils.ReportUsage, true)
    59  	if err != nil {
    60  		log.Debug(ReportUsagePrefix + err.Error())
    61  		return false
    62  	}
    63  	return reportUsage
    64  }
    65  
    66  func (ur *UsageReporter) SetSendToEcosystem(send bool) *UsageReporter {
    67  	ur.sendToEcosystem = send
    68  	return ur
    69  }
    70  
    71  func (ur *UsageReporter) SetSendToXray(send bool) *UsageReporter {
    72  	ur.sendToXray = send
    73  	return ur
    74  }
    75  
    76  func (ur *UsageReporter) SetSendToArtifactory(send bool) *UsageReporter {
    77  	ur.sendToArtifactory = send
    78  	return ur
    79  }
    80  
    81  // Report usage to Artifactory, Xray and Ecosystem
    82  func (ur *UsageReporter) Report(features ...ReportFeature) {
    83  	if !ShouldReportUsage() {
    84  		log.Debug("Usage info is disabled.")
    85  		return
    86  	}
    87  	if len(features) == 0 {
    88  		log.Debug(ReportUsagePrefix, "Nothing to send.")
    89  		return
    90  	}
    91  	log.Debug(ReportUsagePrefix, "Sending info...")
    92  	if ur.sendToEcosystem {
    93  		ur.reportWaitGroup.Go(func() (err error) {
    94  			if err = ur.reportToEcosystem(features...); err != nil {
    95  				err = fmt.Errorf("ecosystem, %w", err)
    96  			}
    97  			return
    98  		})
    99  	}
   100  	if ur.sendToXray {
   101  		ur.reportWaitGroup.Go(func() (err error) {
   102  			if err = ur.reportToXray(features...); err != nil {
   103  				err = fmt.Errorf("xray, %w", err)
   104  			}
   105  			return
   106  		})
   107  	}
   108  	if ur.sendToArtifactory {
   109  		ur.reportWaitGroup.Go(func() (err error) {
   110  			if err = ur.reportToArtifactory(features...); err != nil {
   111  				err = fmt.Errorf("artifactory, %w", err)
   112  			}
   113  			return
   114  		})
   115  	}
   116  }
   117  
   118  func (ur *UsageReporter) WaitForResponses() (err error) {
   119  	if err = ur.reportWaitGroup.Wait(); err != nil {
   120  		err = fmt.Errorf("%s %s", ReportUsagePrefix, err.Error())
   121  	}
   122  	return
   123  }
   124  
   125  func (ur *UsageReporter) reportToEcosystem(features ...ReportFeature) (err error) {
   126  	if ur.serverDetails.Url == "" {
   127  		err = errorutils.CheckErrorf("platform URL is not set")
   128  		return
   129  	}
   130  	reports, err := ur.convertAttributesToEcosystemReports(features...)
   131  	if err != nil {
   132  		return
   133  	}
   134  	if len(reports) == 0 {
   135  		err = errorutils.CheckErrorf("nothing to send")
   136  		return
   137  	}
   138  	return ecosysusage.SendEcosystemUsageReports(reports...)
   139  }
   140  
   141  func (ur *UsageReporter) reportToXray(features ...ReportFeature) (err error) {
   142  	events := ur.convertAttributesToXrayEvents(features...)
   143  	if len(events) == 0 {
   144  		err = errorutils.CheckErrorf("Nothing to send.")
   145  		return
   146  	}
   147  	if ur.serverDetails.XrayUrl == "" {
   148  		err = errorutils.CheckErrorf("Xray Url is not set.")
   149  		return
   150  	}
   151  	serviceManager, err := xrayutils.CreateXrayServiceManager(ur.serverDetails)
   152  	if err != nil {
   153  		return
   154  	}
   155  	return xrayusage.SendXrayUsageEvents(*serviceManager, events...)
   156  }
   157  
   158  func (ur *UsageReporter) reportToArtifactory(features ...ReportFeature) (err error) {
   159  	converted := ur.convertAttributesToArtifactoryFeatures(features...)
   160  	if len(converted) == 0 {
   161  		err = errorutils.CheckErrorf("nothing to send")
   162  		return
   163  	}
   164  	if ur.serverDetails.ArtifactoryUrl == "" {
   165  		err = errorutils.CheckErrorf("Artifactory URL is not set")
   166  		return
   167  	}
   168  	serviceManager, err := utils.CreateServiceManager(ur.serverDetails, -1, 0, false)
   169  	if err != nil {
   170  		return
   171  	}
   172  	return usage.ReportUsageToArtifactory(ur.ProductId, serviceManager, converted...)
   173  }
   174  
   175  func convertAttributesToMap(reportFeature ReportFeature) (converted map[string]string) {
   176  	if len(reportFeature.Attributes) == 0 {
   177  		return
   178  	}
   179  	converted = make(map[string]string, len(reportFeature.Attributes))
   180  	for _, attribute := range reportFeature.Attributes {
   181  		if attribute.AttributeName != "" {
   182  			converted[attribute.AttributeName] = attribute.AttributeValue
   183  		}
   184  	}
   185  	return
   186  }
   187  
   188  func (ur *UsageReporter) convertAttributesToArtifactoryFeatures(reportFeatures ...ReportFeature) (features []usage.Feature) {
   189  	for _, feature := range reportFeatures {
   190  		featureInfo := usage.Feature{
   191  			FeatureId:  feature.FeatureId,
   192  			ClientId:   feature.ClientId,
   193  			Attributes: convertAttributesToMap(feature),
   194  		}
   195  		features = append(features, featureInfo)
   196  	}
   197  	return
   198  }
   199  
   200  func (ur *UsageReporter) convertAttributesToXrayEvents(reportFeatures ...ReportFeature) (events []xrayusage.ReportXrayEventData) {
   201  	for _, feature := range reportFeatures {
   202  		convertedAttributes := []xrayusage.ReportUsageAttribute{}
   203  		for _, attribute := range feature.Attributes {
   204  			convertedAttributes = append(convertedAttributes, xrayusage.ReportUsageAttribute{
   205  				AttributeName:  attribute.AttributeName,
   206  				AttributeValue: attribute.AttributeValue,
   207  			})
   208  		}
   209  		if feature.ClientId != "" {
   210  			// Add clientId as attribute
   211  			convertedAttributes = append(convertedAttributes, xrayusage.ReportUsageAttribute{
   212  				AttributeName:  clientIdAttributeName,
   213  				AttributeValue: feature.ClientId,
   214  			})
   215  		}
   216  		events = append(events, xrayusage.CreateUsageEvent(
   217  			ur.ProductId, feature.FeatureId, convertedAttributes...,
   218  		))
   219  	}
   220  	return
   221  }
   222  
   223  func (ur *UsageReporter) convertAttributesToEcosystemReports(reportFeatures ...ReportFeature) (reports []ecosysusage.ReportEcosystemUsageData, err error) {
   224  	accountId := ur.serverDetails.Url
   225  	clientToFeaturesMap := map[string][]string{}
   226  	// Combine
   227  	for _, feature := range reportFeatures {
   228  		if feature.FeatureId == "" {
   229  			continue
   230  		}
   231  		if features, contains := clientToFeaturesMap[feature.ClientId]; contains {
   232  			clientToFeaturesMap[feature.ClientId] = append(features, feature.FeatureId)
   233  		} else {
   234  			clientToFeaturesMap[feature.ClientId] = []string{feature.FeatureId}
   235  		}
   236  	}
   237  	// Create data
   238  	for clientId, features := range clientToFeaturesMap {
   239  		var report ecosysusage.ReportEcosystemUsageData
   240  		if report, err = ecosysusage.CreateUsageData(ur.ProductId, accountId, clientId, features...); err != nil {
   241  			return
   242  		}
   243  		reports = append(reports, report)
   244  	}
   245  	return
   246  }