github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/exec/check_step.go (about)

     1  package exec
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"time"
     8  
     9  	"code.cloudfoundry.org/lager"
    10  	"code.cloudfoundry.org/lager/lagerctx"
    11  	"github.com/pf-qiu/concourse/v6/atc"
    12  	"github.com/pf-qiu/concourse/v6/atc/creds"
    13  	"github.com/pf-qiu/concourse/v6/atc/db"
    14  	"github.com/pf-qiu/concourse/v6/atc/db/lock"
    15  	"github.com/pf-qiu/concourse/v6/atc/metric"
    16  	"github.com/pf-qiu/concourse/v6/atc/resource"
    17  	"github.com/pf-qiu/concourse/v6/atc/runtime"
    18  	"github.com/pf-qiu/concourse/v6/atc/worker"
    19  	"github.com/pf-qiu/concourse/v6/tracing"
    20  )
    21  
    22  type CheckStep struct {
    23  	planID                atc.PlanID
    24  	plan                  atc.CheckPlan
    25  	metadata              StepMetadata
    26  	containerMetadata     db.ContainerMetadata
    27  	resourceFactory       resource.ResourceFactory
    28  	resourceConfigFactory db.ResourceConfigFactory
    29  	strategy              worker.ContainerPlacementStrategy
    30  	pool                  worker.Pool
    31  	delegateFactory       CheckDelegateFactory
    32  	workerClient          worker.Client
    33  	defaultCheckTimeout   time.Duration
    34  }
    35  
    36  //go:generate counterfeiter . CheckDelegateFactory
    37  
    38  type CheckDelegateFactory interface {
    39  	CheckDelegate(state RunState) CheckDelegate
    40  }
    41  
    42  //go:generate counterfeiter . CheckDelegate
    43  
    44  type CheckDelegate interface {
    45  	BuildStepDelegate
    46  
    47  	FindOrCreateScope(db.ResourceConfig) (db.ResourceConfigScope, error)
    48  	WaitToRun(context.Context, db.ResourceConfigScope) (lock.Lock, bool, error)
    49  	PointToCheckedConfig(db.ResourceConfigScope) error
    50  }
    51  
    52  func NewCheckStep(
    53  	planID atc.PlanID,
    54  	plan atc.CheckPlan,
    55  	metadata StepMetadata,
    56  	resourceFactory resource.ResourceFactory,
    57  	resourceConfigFactory db.ResourceConfigFactory,
    58  	containerMetadata db.ContainerMetadata,
    59  	strategy worker.ContainerPlacementStrategy,
    60  	pool worker.Pool,
    61  	delegateFactory CheckDelegateFactory,
    62  	client worker.Client,
    63  	defaultCheckTimeout time.Duration,
    64  ) Step {
    65  	return &CheckStep{
    66  		planID:                planID,
    67  		plan:                  plan,
    68  		metadata:              metadata,
    69  		resourceFactory:       resourceFactory,
    70  		resourceConfigFactory: resourceConfigFactory,
    71  		containerMetadata:     containerMetadata,
    72  		pool:                  pool,
    73  		strategy:              strategy,
    74  		delegateFactory:       delegateFactory,
    75  		workerClient:          client,
    76  		defaultCheckTimeout:   defaultCheckTimeout,
    77  	}
    78  }
    79  
    80  func (step *CheckStep) Run(ctx context.Context, state RunState) (bool, error) {
    81  	attrs := tracing.Attrs{
    82  		"name": step.plan.Name,
    83  	}
    84  
    85  	if step.plan.Resource != "" {
    86  		attrs["resource"] = step.plan.Resource
    87  	}
    88  
    89  	if step.plan.ResourceType != "" {
    90  		attrs["resource_type"] = step.plan.ResourceType
    91  	}
    92  
    93  	delegate := step.delegateFactory.CheckDelegate(state)
    94  	ctx, span := delegate.StartSpan(ctx, "check", attrs)
    95  
    96  	ok, err := step.run(ctx, state, delegate)
    97  	tracing.End(span, err)
    98  
    99  	return ok, err
   100  }
   101  
   102  func (step *CheckStep) run(ctx context.Context, state RunState, delegate CheckDelegate) (bool, error) {
   103  	logger := lagerctx.FromContext(ctx)
   104  	logger = logger.Session("check-step", lager.Data{
   105  		"step-name": step.plan.Name,
   106  	})
   107  
   108  	delegate.Initializing(logger)
   109  
   110  	timeout := step.defaultCheckTimeout
   111  	if step.plan.Timeout != "" {
   112  		var err error
   113  		timeout, err = time.ParseDuration(step.plan.Timeout)
   114  		if err != nil {
   115  			return false, fmt.Errorf("parse timeout: %w", err)
   116  		}
   117  	}
   118  
   119  	source, err := creds.NewSource(state, step.plan.Source).Evaluate()
   120  	if err != nil {
   121  		return false, fmt.Errorf("resource config creds evaluation: %w", err)
   122  	}
   123  
   124  	resourceTypes, err := creds.NewVersionedResourceTypes(state, step.plan.VersionedResourceTypes).Evaluate()
   125  	if err != nil {
   126  		return false, fmt.Errorf("resource types creds evaluation: %w", err)
   127  	}
   128  
   129  	resourceConfig, err := step.resourceConfigFactory.FindOrCreateResourceConfig(step.plan.Type, source, resourceTypes)
   130  	if err != nil {
   131  		return false, fmt.Errorf("create resource config: %w", err)
   132  	}
   133  
   134  	// XXX(check-refactor): we should remove scopes as soon as it's safe to do
   135  	// so, i.e. global resources is on by default. i think this can be done when
   136  	// time resource becomes time var source (resolving thundering herd problem)
   137  	// and IAM is handled via var source prototypes (resolving unintentionally
   138  	// shared history problem)
   139  	scope, err := delegate.FindOrCreateScope(resourceConfig)
   140  	if err != nil {
   141  		return false, fmt.Errorf("create resource config scope: %w", err)
   142  	}
   143  
   144  	lock, run, err := delegate.WaitToRun(ctx, scope)
   145  	if err != nil {
   146  		return false, fmt.Errorf("wait: %w", err)
   147  	}
   148  
   149  	if run {
   150  		defer func() {
   151  			err := lock.Release()
   152  			if err != nil {
   153  				logger.Error("failed-to-release-lock", err)
   154  			}
   155  		}()
   156  
   157  		fromVersion := step.plan.FromVersion
   158  		if fromVersion == nil {
   159  			latestVersion, found, err := scope.LatestVersion()
   160  			if err != nil {
   161  				return false, fmt.Errorf("get latest version: %w", err)
   162  			}
   163  
   164  			if found {
   165  				fromVersion = atc.Version(latestVersion.Version())
   166  			}
   167  		}
   168  
   169  		metric.Metrics.ChecksStarted.Inc()
   170  
   171  		_, err = scope.UpdateLastCheckStartTime()
   172  		if err != nil {
   173  			return false, fmt.Errorf("update check end time: %w", err)
   174  		}
   175  
   176  		result, err := step.runCheck(ctx, logger, delegate, timeout, resourceConfig, source, resourceTypes, fromVersion)
   177  		if err != nil {
   178  			metric.Metrics.ChecksFinishedWithError.Inc()
   179  
   180  			if _, updateErr := scope.UpdateLastCheckEndTime(); updateErr != nil {
   181  				return false, fmt.Errorf("update check end time: %w", updateErr)
   182  			}
   183  
   184  			if pointErr := delegate.PointToCheckedConfig(scope); pointErr != nil {
   185  				return false, fmt.Errorf("update resource config scope: %w", pointErr)
   186  			}
   187  
   188  			var scriptErr runtime.ErrResourceScriptFailed
   189  			if errors.As(err, &scriptErr) {
   190  				delegate.Finished(logger, false)
   191  				return false, nil
   192  			}
   193  
   194  			return false, fmt.Errorf("run check: %w", err)
   195  		}
   196  
   197  		metric.Metrics.ChecksFinishedWithSuccess.Inc()
   198  
   199  		err = scope.SaveVersions(db.NewSpanContext(ctx), result.Versions)
   200  		if err != nil {
   201  			return false, fmt.Errorf("save versions: %w", err)
   202  		}
   203  
   204  		if len(result.Versions) > 0 {
   205  			state.StoreResult(step.planID, result.Versions[len(result.Versions)-1])
   206  		}
   207  
   208  		_, err = scope.UpdateLastCheckEndTime()
   209  		if err != nil {
   210  			return false, fmt.Errorf("update check end time: %w", err)
   211  		}
   212  	} else {
   213  		latestVersion, found, err := scope.LatestVersion()
   214  		if err != nil {
   215  			return false, fmt.Errorf("get latest version: %w", err)
   216  		}
   217  
   218  		if found {
   219  			state.StoreResult(step.planID, atc.Version(latestVersion.Version()))
   220  		}
   221  	}
   222  
   223  	err = delegate.PointToCheckedConfig(scope)
   224  	if err != nil {
   225  		return false, fmt.Errorf("update resource config scope: %w", err)
   226  	}
   227  
   228  	delegate.Finished(logger, true)
   229  
   230  	return true, nil
   231  }
   232  
   233  func (step *CheckStep) runCheck(
   234  	ctx context.Context,
   235  	logger lager.Logger,
   236  	delegate CheckDelegate,
   237  	timeout time.Duration,
   238  	resourceConfig db.ResourceConfig,
   239  	source atc.Source,
   240  	resourceTypes atc.VersionedResourceTypes,
   241  	fromVersion atc.Version,
   242  ) (worker.CheckResult, error) {
   243  	workerSpec := worker.WorkerSpec{
   244  		Tags:         step.plan.Tags,
   245  		TeamID:       step.metadata.TeamID,
   246  		ResourceType: step.plan.VersionedResourceTypes.Base(step.plan.Type),
   247  	}
   248  
   249  	var imageSpec worker.ImageSpec
   250  	resourceType, found := step.plan.VersionedResourceTypes.Lookup(step.plan.Type)
   251  	if found {
   252  		image := atc.ImageResource{
   253  			Name:    resourceType.Name,
   254  			Type:    resourceType.Type,
   255  			Source:  resourceType.Source,
   256  			Params:  resourceType.Params,
   257  			Version: resourceType.Version,
   258  			Tags:    resourceType.Tags,
   259  		}
   260  		if len(image.Tags) == 0 {
   261  			image.Tags = step.plan.Tags
   262  		}
   263  
   264  		types := step.plan.VersionedResourceTypes.Without(step.plan.Type)
   265  
   266  		var err error
   267  		imageSpec, err = delegate.FetchImage(ctx, image, types, resourceType.Privileged)
   268  		if err != nil {
   269  			return worker.CheckResult{}, err
   270  		}
   271  	} else {
   272  		imageSpec.ResourceType = step.plan.Type
   273  	}
   274  
   275  	containerSpec := worker.ContainerSpec{
   276  		ImageSpec: imageSpec,
   277  		BindMounts: []worker.BindMountSource{
   278  			&worker.CertsVolumeMount{Logger: logger},
   279  		},
   280  		TeamID: step.metadata.TeamID,
   281  		Env:    step.metadata.Env(),
   282  	}
   283  	tracing.Inject(ctx, &containerSpec)
   284  
   285  	checkable := step.resourceFactory.NewResource(
   286  		source,
   287  		nil,
   288  		fromVersion,
   289  	)
   290  
   291  	processSpec := runtime.ProcessSpec{
   292  		Path:         "/opt/resource/check",
   293  		StdoutWriter: delegate.Stdout(),
   294  		StderrWriter: delegate.Stderr(),
   295  	}
   296  
   297  	return step.workerClient.RunCheckStep(
   298  		ctx,
   299  		logger,
   300  		step.containerOwner(resourceConfig),
   301  		containerSpec,
   302  		workerSpec,
   303  		step.strategy,
   304  		step.containerMetadata,
   305  		processSpec,
   306  		delegate,
   307  		checkable,
   308  		timeout,
   309  	)
   310  }
   311  
   312  func (step *CheckStep) containerOwner(resourceConfig db.ResourceConfig) db.ContainerOwner {
   313  	if step.plan.Resource == "" {
   314  		return db.NewBuildStepContainerOwner(
   315  			step.metadata.BuildID,
   316  			step.planID,
   317  			step.metadata.TeamID,
   318  		)
   319  	}
   320  
   321  	expires := db.ContainerOwnerExpiries{
   322  		Min: 5 * time.Minute,
   323  		Max: 1 * time.Hour,
   324  	}
   325  
   326  	// XXX(check-refactor): this can be turned into NewBuildStepContainerOwner
   327  	// now, but we should understand the performance implications first - it'll
   328  	// mean a lot more container churn
   329  	return db.NewResourceConfigCheckSessionContainerOwner(
   330  		resourceConfig.ID(),
   331  		resourceConfig.OriginBaseResourceType().ID,
   332  		expires,
   333  	)
   334  }