github.com/chenbh/concourse/v6@v6.4.2/atc/lidar/scanner.go (about)

     1  package lidar
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"runtime/debug"
     8  	"strconv"
     9  	"sync"
    10  	"time"
    11  
    12  	"code.cloudfoundry.org/lager"
    13  	"code.cloudfoundry.org/lager/lagerctx"
    14  	"github.com/chenbh/concourse/v6/atc/creds"
    15  	"github.com/chenbh/concourse/v6/atc/db"
    16  	"github.com/chenbh/concourse/v6/atc/metric"
    17  	"github.com/chenbh/concourse/v6/tracing"
    18  	"github.com/pkg/errors"
    19  )
    20  
    21  func NewScanner(
    22  	logger lager.Logger,
    23  	checkFactory db.CheckFactory,
    24  	secrets creds.Secrets,
    25  	defaultCheckTimeout time.Duration,
    26  	defaultCheckInterval time.Duration,
    27  	defaultWithWebhookCheckInterval time.Duration,
    28  ) *scanner {
    29  	return &scanner{
    30  		logger:                          logger,
    31  		checkFactory:                    checkFactory,
    32  		secrets:                         secrets,
    33  		defaultCheckTimeout:             defaultCheckTimeout,
    34  		defaultCheckInterval:            defaultCheckInterval,
    35  		defaultWithWebhookCheckInterval: defaultWithWebhookCheckInterval,
    36  	}
    37  }
    38  
    39  type scanner struct {
    40  	logger lager.Logger
    41  
    42  	checkFactory                    db.CheckFactory
    43  	secrets                         creds.Secrets
    44  	defaultCheckTimeout             time.Duration
    45  	defaultCheckInterval            time.Duration
    46  	defaultWithWebhookCheckInterval time.Duration
    47  }
    48  
    49  func (s *scanner) Run(ctx context.Context) error {
    50  	spanCtx, span := tracing.StartSpan(ctx, "scanner.Run", nil)
    51  	s.logger.Info("start")
    52  	defer span.End()
    53  	defer s.logger.Info("end")
    54  
    55  	resources, err := s.checkFactory.Resources()
    56  	if err != nil {
    57  		s.logger.Error("failed-to-get-resources", err)
    58  		return err
    59  	}
    60  
    61  	resourceTypes, err := s.checkFactory.ResourceTypes()
    62  	if err != nil {
    63  		s.logger.Error("failed-to-get-resource-types", err)
    64  		return err
    65  	}
    66  
    67  	waitGroup := new(sync.WaitGroup)
    68  	resourceTypesChecked := &sync.Map{}
    69  
    70  	for _, resource := range resources {
    71  		waitGroup.Add(1)
    72  
    73  		go func(resource db.Resource, resourceTypes db.ResourceTypes) {
    74  			loggerData := lager.Data{
    75  				"resource_id":   strconv.Itoa(resource.ID()),
    76  				"resource_name": resource.Name(),
    77  				"pipeline_name": resource.PipelineName(),
    78  				"team_name":     resource.TeamName(),
    79  			}
    80  			defer func() {
    81  				if r := recover(); r != nil {
    82  					err = fmt.Errorf("panic in scanner run %s: %v", loggerData, r)
    83  
    84  					fmt.Fprintf(os.Stderr, "%s\n %s\n", err.Error(), string(debug.Stack()))
    85  					s.logger.Error("panic-in-scanner-run", err)
    86  
    87  					s.setCheckError(s.logger, resource, err)
    88  				}
    89  			}()
    90  			defer waitGroup.Done()
    91  
    92  			err := s.check(spanCtx, resource, resourceTypes, resourceTypesChecked)
    93  			s.setCheckError(s.logger, resource, err)
    94  
    95  		}(resource, resourceTypes)
    96  	}
    97  
    98  	waitGroup.Wait()
    99  
   100  	return s.checkFactory.NotifyChecker()
   101  }
   102  
   103  func (s *scanner) check(ctx context.Context, checkable db.Checkable, resourceTypes db.ResourceTypes, resourceTypesChecked *sync.Map) error {
   104  
   105  	var err error
   106  
   107  	spanCtx, span := tracing.StartSpan(ctx, "scanner.check", tracing.Attrs{
   108  		"team":                     checkable.TeamName(),
   109  		"pipeline":                 checkable.PipelineName(),
   110  		"resource":                 checkable.Name(),
   111  		"type":                     checkable.Type(),
   112  		"resource_config_scope_id": strconv.Itoa(checkable.ResourceConfigScopeID()),
   113  	})
   114  	defer span.End()
   115  
   116  	parentType, found := resourceTypes.Parent(checkable)
   117  	if found {
   118  		if _, exists := resourceTypesChecked.LoadOrStore(parentType.ID(), true); !exists {
   119  			// only create a check for resource type if it has not been checked yet
   120  			err = s.check(spanCtx, parentType, resourceTypes, resourceTypesChecked)
   121  			s.setCheckError(s.logger, parentType, err)
   122  
   123  			if err != nil {
   124  				s.logger.Error("failed-to-create-type-check", err)
   125  				return errors.Wrapf(err, "parent type '%v' error", parentType.Name())
   126  			}
   127  		}
   128  	}
   129  
   130  	interval := s.defaultCheckInterval
   131  	if checkable.HasWebhook() {
   132  		interval = s.defaultWithWebhookCheckInterval
   133  	}
   134  	if every := checkable.CheckEvery(); every != "" {
   135  		interval, err = time.ParseDuration(every)
   136  		if err != nil {
   137  			s.logger.Error("failed-to-parse-check-every", err)
   138  			return err
   139  		}
   140  	}
   141  
   142  	if time.Now().Before(checkable.LastCheckEndTime().Add(interval)) {
   143  		return nil
   144  	}
   145  
   146  	version := checkable.CurrentPinnedVersion()
   147  
   148  	_, created, err := s.checkFactory.TryCreateCheck(lagerctx.NewContext(spanCtx, s.logger), checkable, resourceTypes, version, false)
   149  	if err != nil {
   150  		s.logger.Error("failed-to-create-check", err)
   151  		return err
   152  	}
   153  
   154  	if !created {
   155  		s.logger.Debug("check-already-exists")
   156  	}
   157  
   158  	metric.ChecksEnqueued.Inc()
   159  
   160  	return nil
   161  }
   162  
   163  func (s *scanner) setCheckError(logger lager.Logger, checkable db.Checkable, err error) {
   164  	setErr := checkable.SetCheckSetupError(err)
   165  	if setErr != nil {
   166  		logger.Error("failed-to-set-check-error", setErr)
   167  	}
   168  }