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

     1  package cmd
     2  
     3  import (
     4  	"encoding/json"
     5  
     6  	"github.com/Axway/agent-sdk/pkg/config"
     7  	"github.com/Axway/agent-sdk/pkg/util"
     8  
     9  	"regexp"
    10  	"strconv"
    11  	"strings"
    12  
    13  	"net/http"
    14  
    15  	coreapi "github.com/Axway/agent-sdk/pkg/api"
    16  	"github.com/Axway/agent-sdk/pkg/jobs"
    17  	"github.com/Axway/agent-sdk/pkg/util/errors"
    18  	log "github.com/Axway/agent-sdk/pkg/util/log"
    19  )
    20  
    21  const (
    22  	avcCronSchedule = "@daily"
    23  	jfrogURL        = "https://axway.jfrog.io/ui/api/v1/ui/treebrowser"
    24  )
    25  
    26  var agentNameToRepoPath = map[string]string{
    27  	"AWSDiscoveryAgent":                      "aws-apigw-discovery-agent",
    28  	"AWSTraceabilityAgent":                   "aws-apigw-traceability-agent",
    29  	"AzureDiscoveryAgent":                    "azure-discovery-agent",
    30  	"AzureTraceabilityAgent":                 "azure-traceability-agent",
    31  	"EnterpriseEdgeGatewayDiscoveryAgent":    "v7-discovery-agent",
    32  	"EnterpriseEdgeGatewayTraceabilityAgent": "v7-traceability-agent",
    33  }
    34  
    35  type version struct {
    36  	major, minor, patch int
    37  	val                 string
    38  }
    39  
    40  type jfrogRequest struct {
    41  	Type     string `json:"type"`
    42  	RepoType string `json:"repoType"`
    43  	RepoKey  string `json:"repoKey"`
    44  	Path     string `json:"path"`
    45  	Text     string `json:"text"`
    46  }
    47  
    48  type jfrogData struct {
    49  	Items []jfrogItem `json:"data"`
    50  }
    51  
    52  type jfrogItem struct {
    53  	RepoKey      string `json:"repoKey,omitempty"`
    54  	Path         string `json:"path,omitempty"`
    55  	Version      string `json:"text"`
    56  	RepoType     string `json:"repoType,omitempty"`
    57  	HasChild     bool   `json:"hasChild,omitempty"`
    58  	Local        bool   `json:"local,omitempty"`
    59  	Type         string `json:"type,omitempty"`
    60  	Compacted    bool   `json:"compacted,omitempty"`
    61  	Cached       bool   `json:"cached,omitempty"`
    62  	Trash        bool   `json:"trash,omitempty"`
    63  	Distribution bool   `json:"distribution,omitempty"`
    64  }
    65  
    66  // AgentVersionCheckJob - polls for agent versions
    67  type AgentVersionCheckJob struct {
    68  	jobs.Job
    69  	apiClient    coreapi.Client
    70  	requestBytes []byte
    71  	buildVersion string
    72  	headers      map[string]string
    73  }
    74  
    75  // NewAgentVersionCheckJob - creates a new agent version check job structure
    76  func NewAgentVersionCheckJob(cfg config.CentralConfig) (*AgentVersionCheckJob, error) {
    77  	// get current build version
    78  	buildVersion, err := getBuildVersion()
    79  	if err != nil {
    80  		log.Trace(err)
    81  		return nil, err
    82  	}
    83  
    84  	if _, found := agentNameToRepoPath[BuildAgentName]; !found {
    85  		err := errors.ErrStartingVersionChecker.FormatError("empty or generic data plane type name")
    86  		log.Trace(err)
    87  		return nil, err
    88  	}
    89  
    90  	// create the request body for each check
    91  	requestBody := jfrogRequest{
    92  		Type:     "junction",
    93  		RepoType: "virtual",
    94  		RepoKey:  "ampc-public-docker-release",
    95  		Path:     "agent/" + agentNameToRepoPath[BuildAgentName],
    96  		Text:     agentNameToRepoPath[BuildAgentName],
    97  	}
    98  	requestBytes, err := json.Marshal(requestBody)
    99  	if err != nil {
   100  		log.Trace(err)
   101  		return nil, err
   102  	}
   103  
   104  	return &AgentVersionCheckJob{
   105  		apiClient: coreapi.NewClient(cfg.GetTLSConfig(), cfg.GetProxyURL(),
   106  			coreapi.WithTimeout(cfg.GetClientTimeout()),
   107  			coreapi.WithSingleURL()),
   108  		buildVersion: buildVersion,
   109  		requestBytes: requestBytes,
   110  		headers: map[string]string{
   111  			"X-Requested-With": "XMLHttpRequest",
   112  			"Host":             "axway.jfrog.io",
   113  			"Content-Length":   strconv.Itoa(len(requestBytes)),
   114  			"Content-Type":     "application/json",
   115  		},
   116  	}, nil
   117  }
   118  
   119  // Ready -
   120  func (avj *AgentVersionCheckJob) Ready() bool {
   121  	return true
   122  }
   123  
   124  // Status -
   125  func (avj *AgentVersionCheckJob) Status() error {
   126  	return nil
   127  }
   128  
   129  // Execute - run agent version check job one time
   130  func (avj *AgentVersionCheckJob) Execute() error {
   131  	err := avj.getJFrogVersions()
   132  	if err != nil {
   133  		log.Trace(err)
   134  		// Could not get update from jfrog.  Warn that we could not determine version and continue processing
   135  		log.Warn("Agent cannot determine the next available release. Be aware that your agent could be outdated.")
   136  	} else {
   137  		// Successfully got jfrog version.  Now compare build to latest version
   138  		if isVersionStringOlder(avj.buildVersion, config.AgentLatestVersion) {
   139  			log.Warnf("New version available. Please consider upgrading from version %s to version %s", avj.buildVersion, config.AgentLatestVersion)
   140  		}
   141  	}
   142  	return nil
   143  }
   144  
   145  // getJFrogVersions - obtaining the versions from JFrog website
   146  // **Note** polling the jfrog website is the current solution to obtaining the list of versions
   147  // In the future, adding a (Generic) resource for grouping versions together under the same scope is a possible solution
   148  // ie: a new unscoped resource that represents the platform services, so that other products can plug in their releases.
   149  func (avj *AgentVersionCheckJob) getJFrogVersions() error {
   150  	request := coreapi.Request{
   151  		Method:  http.MethodPost,
   152  		URL:     jfrogURL,
   153  		Headers: avj.headers,
   154  		Body:    avj.requestBytes,
   155  	}
   156  	response, err := avj.apiClient.Send(request)
   157  	if err != nil {
   158  		return err
   159  	}
   160  
   161  	jfrogResponse := jfrogData{}
   162  	err = json.Unmarshal(response.Body, &jfrogResponse)
   163  	if err != nil {
   164  		return err
   165  	}
   166  
   167  	config.AgentLatestVersion = avj.getLatestVersionFromJFrog(jfrogResponse.Items)
   168  	return nil
   169  }
   170  
   171  func getBuildVersion() (string, error) {
   172  	//remove -SHA from build version
   173  	versionNoSHA := strings.Split(BuildVersion, "-")[0]
   174  
   175  	//regex check for semantic versioning
   176  	semVerRegexp := regexp.MustCompile(`\d.\d.\d`)
   177  	if versionNoSHA == "" || !semVerRegexp.MatchString(versionNoSHA) {
   178  		return "", errors.ErrStartingVersionChecker.FormatError("build version is missing or of noncompliant semantic versioning")
   179  	}
   180  	return versionNoSHA, nil
   181  }
   182  
   183  // isVersionStringOlder - return true if version of str1 is older than str2
   184  func isVersionStringOlder(build string, latest string) bool {
   185  	vB := getSemVer(build)
   186  	vL := getSemVer(latest)
   187  
   188  	return isVersionSmaller(vB, vL)
   189  }
   190  
   191  // isVersionSmaller - return true if version1 smaller than version2
   192  func isVersionSmaller(v1 version, v2 version) bool {
   193  	if v1.major < v2.major {
   194  		return true
   195  	}
   196  	if v1.major == v2.major {
   197  		if v1.minor < v2.minor {
   198  			return true
   199  		}
   200  		if v1.minor == v2.minor && v1.patch < v2.patch {
   201  			return true
   202  		}
   203  	}
   204  	return false
   205  }
   206  
   207  func (avj *AgentVersionCheckJob) getLatestVersionFromJFrog(jfrogItems []jfrogItem) string {
   208  	tempMaxVersion := version{
   209  		major: 0,
   210  		minor: 0,
   211  		patch: 0,
   212  		val:   "",
   213  	}
   214  	re := regexp.MustCompile(`\d{8}`)
   215  
   216  	for _, item := range jfrogItems {
   217  		//trimming version from jfrog webpage
   218  		if item.Version != "latest" && item.Version != "" {
   219  			v := getSemVer(item.Version)
   220  			// avoid a version with an 8 digit date as the patch number: 1.0.20210421
   221  			if !re.MatchString(strconv.Itoa(v.patch)) && isVersionSmaller(tempMaxVersion, v) {
   222  				copyVersionStruct(&tempMaxVersion, v)
   223  			}
   224  		}
   225  	}
   226  	return tempMaxVersion.val
   227  }
   228  
   229  // getSemVer - getting a semantic version struct from version string
   230  // pre-req is that string is already in semantic versioning with major, minor, and patch
   231  func getSemVer(str string) version {
   232  	s := strings.Split(str, ".")
   233  	maj, err := strconv.Atoi(s[0])
   234  	min, err2 := strconv.Atoi(s[1])
   235  	pat, err3 := strconv.Atoi(s[2])
   236  	if err == nil && err2 == nil && err3 == nil {
   237  		v := version{
   238  			major: maj,
   239  			minor: min,
   240  			patch: pat,
   241  			val:   str,
   242  		}
   243  		return v
   244  	}
   245  	return version{}
   246  }
   247  
   248  // copyVersionStruct - copying version2 into version1 struct by value
   249  func copyVersionStruct(v1 *version, v2 version) {
   250  	v1.major = v2.major
   251  	v1.minor = v2.minor
   252  	v1.patch = v2.patch
   253  	v1.val = v2.val
   254  }
   255  
   256  // startVersionCheckJobs - starts both a single run and continuous checks
   257  func startVersionCheckJobs(cfg config.CentralConfig, agentFeaturesCfg config.AgentFeaturesConfig) {
   258  	if !util.IsNotTest() || !agentFeaturesCfg.VersionCheckerEnabled() {
   259  		return
   260  	}
   261  	// register the agent version checker single run job
   262  	checkJob, err := NewAgentVersionCheckJob(cfg)
   263  	if err != nil {
   264  		log.Errorf("could not create the agent version checker: %v", err.Error())
   265  		return
   266  	}
   267  	if id, err := jobs.RegisterSingleRunJobWithName(checkJob, "Version Check"); err == nil {
   268  		log.Tracef("registered agent version checker job: %s", id)
   269  	}
   270  	if id, err := jobs.RegisterScheduledJobWithName(checkJob, avcCronSchedule, "Version Check Schedule"); err == nil {
   271  		log.Tracef("registered agent version checker cronjob: %s", id)
   272  	}
   273  }