github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/db/dbtest/builder.go (about)

     1  package dbtest
     2  
     3  import (
     4  	"crypto/md5"
     5  	"encoding/hex"
     6  	"encoding/json"
     7  	"fmt"
     8  	"time"
     9  
    10  	"github.com/pf-qiu/concourse/v6/atc"
    11  	"github.com/pf-qiu/concourse/v6/atc/db"
    12  	"github.com/pf-qiu/concourse/v6/atc/db/lock"
    13  	uuid "github.com/nu7hatch/gouuid"
    14  )
    15  
    16  const BaseResourceType = "global-base-type"
    17  const UniqueBaseResourceType = "unique-base-type"
    18  
    19  type JobInputs []JobInput
    20  
    21  type JobInput struct {
    22  	Name            string
    23  	Version         atc.Version
    24  	PassedBuilds    []db.Build
    25  	FirstOccurrence bool
    26  
    27  	ResolveError string
    28  }
    29  
    30  func (inputs JobInputs) Lookup(name string) (JobInput, bool) {
    31  	for _, i := range inputs {
    32  		if i.Name == name {
    33  			return i, true
    34  		}
    35  	}
    36  
    37  	return JobInput{}, false
    38  }
    39  
    40  type JobOutputs map[string]atc.Version
    41  
    42  type Builder struct {
    43  	TeamFactory           db.TeamFactory
    44  	WorkerFactory         db.WorkerFactory
    45  	ResourceConfigFactory db.ResourceConfigFactory
    46  }
    47  
    48  func NewBuilder(conn db.Conn, lockFactory lock.LockFactory) Builder {
    49  	return Builder{
    50  		TeamFactory:           db.NewTeamFactory(conn, lockFactory),
    51  		WorkerFactory:         db.NewWorkerFactory(conn),
    52  		ResourceConfigFactory: db.NewResourceConfigFactory(conn, lockFactory),
    53  	}
    54  }
    55  
    56  func (builder Builder) WithTeam(teamName string) SetupFunc {
    57  	return func(scenario *Scenario) error {
    58  		t, err := builder.TeamFactory.CreateTeam(atc.Team{Name: teamName})
    59  		if err != nil {
    60  			return err
    61  		}
    62  
    63  		scenario.Team = t
    64  		return nil
    65  	}
    66  }
    67  
    68  func (builder Builder) WithWorker(worker atc.Worker) SetupFunc {
    69  	return func(scenario *Scenario) error {
    70  		var w db.Worker
    71  		var err error
    72  		if worker.Team != "" {
    73  			team, found, err := builder.TeamFactory.FindTeam(worker.Team)
    74  			if err != nil {
    75  				return err
    76  			}
    77  
    78  			if !found {
    79  				return fmt.Errorf("team does not exist: %s", worker.Team)
    80  			}
    81  
    82  			w, err = team.SaveWorker(worker, 0)
    83  		} else {
    84  			w, err = builder.WorkerFactory.SaveWorker(worker, 0)
    85  		}
    86  		if err != nil {
    87  			return err
    88  		}
    89  
    90  		scenario.Workers = append(scenario.Workers, w)
    91  		return nil
    92  	}
    93  }
    94  
    95  func (builder Builder) WithPipeline(config atc.Config) SetupFunc {
    96  	return func(scenario *Scenario) error {
    97  		if scenario.Team == nil {
    98  			err := builder.WithTeam(unique("team"))(scenario)
    99  			if err != nil {
   100  				return fmt.Errorf("bootstrap team: %w", err)
   101  			}
   102  		}
   103  
   104  		var from db.ConfigVersion
   105  		if scenario.Pipeline != nil {
   106  			from = scenario.Pipeline.ConfigVersion()
   107  		}
   108  
   109  		p, _, err := scenario.Team.SavePipeline(atc.PipelineRef{Name: "some-pipeline"}, config, from, false)
   110  		if err != nil {
   111  			return err
   112  		}
   113  
   114  		scenario.Pipeline = p
   115  		return nil
   116  	}
   117  }
   118  
   119  func (builder Builder) WithBaseWorker() SetupFunc {
   120  	return builder.WithWorker(atc.Worker{
   121  		Name: unique("worker"),
   122  
   123  		GardenAddr:      unique("garden-addr"),
   124  		BaggageclaimURL: unique("baggageclaim-url"),
   125  
   126  		ResourceTypes: []atc.WorkerResourceType{
   127  			{
   128  				Type:    BaseResourceType,
   129  				Image:   "/path/to/global/image",
   130  				Version: "some-global-type-version",
   131  			},
   132  			{
   133  				Type:                 UniqueBaseResourceType,
   134  				Image:                "/path/to/unique/image",
   135  				Version:              "some-unique-type-version",
   136  				UniqueVersionHistory: true,
   137  			},
   138  		},
   139  	})
   140  }
   141  
   142  func (builder Builder) WithResourceVersions(resourceName string, versions ...atc.Version) SetupFunc {
   143  	return func(scenario *Scenario) error {
   144  		if scenario.Pipeline == nil {
   145  			err := builder.WithPipeline(atc.Config{
   146  				Resources: atc.ResourceConfigs{
   147  					{
   148  						Name:   resourceName,
   149  						Type:   BaseResourceType,
   150  						Source: atc.Source{"some": "source"},
   151  					},
   152  				},
   153  			})(scenario)
   154  			if err != nil {
   155  				return fmt.Errorf("bootstrap pipeline: %w", err)
   156  			}
   157  		}
   158  
   159  		if len(scenario.Workers) == 0 {
   160  			err := builder.WithBaseWorker()(scenario)
   161  			if err != nil {
   162  				return fmt.Errorf("bootstrap workers: %w", err)
   163  			}
   164  		}
   165  
   166  		resource, found, err := scenario.Pipeline.Resource(resourceName)
   167  		if err != nil {
   168  			return err
   169  		}
   170  
   171  		if !found {
   172  			return fmt.Errorf("resource '%s' not configured in pipeline", resourceName)
   173  		}
   174  
   175  		resourceTypes, err := scenario.Pipeline.ResourceTypes()
   176  		if err != nil {
   177  			return fmt.Errorf("get pipeline resource types: %w", err)
   178  		}
   179  
   180  		resourceConfig, err := builder.ResourceConfigFactory.FindOrCreateResourceConfig(
   181  			resource.Type(),
   182  			resource.Source(),
   183  			resourceTypes.Deserialize(),
   184  		)
   185  		if err != nil {
   186  			return fmt.Errorf("find or create resource config: %w", err)
   187  		}
   188  
   189  		scope, err := resourceConfig.FindOrCreateScope(resource)
   190  		if err != nil {
   191  			return fmt.Errorf("find or create scope: %w", err)
   192  		}
   193  
   194  		err = scope.SaveVersions(scenario.SpanContext, versions)
   195  		if err != nil {
   196  			return fmt.Errorf("save versions: %w", err)
   197  		}
   198  
   199  		_, err = scope.UpdateLastCheckEndTime()
   200  		if err != nil {
   201  			return fmt.Errorf("update last check end time: %w", err)
   202  		}
   203  
   204  		err = resource.SetResourceConfigScope(scope)
   205  		if err != nil {
   206  			return fmt.Errorf("set resource scope: %w", err)
   207  		}
   208  
   209  		return nil
   210  	}
   211  }
   212  
   213  func (builder Builder) WithResourceTypeVersions(resourceTypeName string, versions ...atc.Version) SetupFunc {
   214  	return func(scenario *Scenario) error {
   215  		if scenario.Pipeline == nil {
   216  			err := builder.WithPipeline(atc.Config{
   217  				ResourceTypes: atc.ResourceTypes{
   218  					{
   219  						Name:   resourceTypeName,
   220  						Type:   BaseResourceType,
   221  						Source: atc.Source{"some": "source"},
   222  					},
   223  				},
   224  			})(scenario)
   225  			if err != nil {
   226  				return fmt.Errorf("bootstrap pipeline: %w", err)
   227  			}
   228  		}
   229  
   230  		if len(scenario.Workers) == 0 {
   231  			err := builder.WithBaseWorker()(scenario)
   232  			if err != nil {
   233  				return fmt.Errorf("bootstrap workers: %w", err)
   234  			}
   235  		}
   236  
   237  		resourceType, found, err := scenario.Pipeline.ResourceType(resourceTypeName)
   238  		if err != nil {
   239  			return err
   240  		}
   241  
   242  		if !found {
   243  			return fmt.Errorf("resource type '%s' not configured in pipeline", resourceTypeName)
   244  		}
   245  
   246  		resourceTypes, err := scenario.Pipeline.ResourceTypes()
   247  		if err != nil {
   248  			return fmt.Errorf("get pipeline resource types: %w", err)
   249  		}
   250  
   251  		resourceConfig, err := builder.ResourceConfigFactory.FindOrCreateResourceConfig(
   252  			resourceType.Type(),
   253  			resourceType.Source(),
   254  			resourceTypes.Filter(resourceType).Deserialize(),
   255  		)
   256  		if err != nil {
   257  			return fmt.Errorf("find or create resource config: %w", err)
   258  		}
   259  
   260  		scope, err := resourceConfig.FindOrCreateScope(nil)
   261  		if err != nil {
   262  			return fmt.Errorf("find or create scope: %w", err)
   263  		}
   264  
   265  		err = scope.SaveVersions(db.SpanContext{}, versions)
   266  		if err != nil {
   267  			return fmt.Errorf("save versions: %w", err)
   268  		}
   269  
   270  		resourceType.SetResourceConfigScope(scope)
   271  		if err != nil {
   272  			return fmt.Errorf("set resource scope: %w", err)
   273  		}
   274  
   275  		return nil
   276  	}
   277  }
   278  
   279  func (builder Builder) WithPendingJobBuild(assign *db.Build, jobName string) SetupFunc {
   280  	return func(scenario *Scenario) error {
   281  		if scenario.Pipeline == nil {
   282  			return fmt.Errorf("no pipeline set in scenario")
   283  		}
   284  
   285  		job, found, err := scenario.Pipeline.Job(jobName)
   286  		if err != nil {
   287  			return err
   288  		}
   289  
   290  		if !found {
   291  			return fmt.Errorf("job '%s' not configured in pipeline", jobName)
   292  		}
   293  
   294  		build, err := job.CreateBuild()
   295  		if err != nil {
   296  			return fmt.Errorf("create build: %w", err)
   297  		}
   298  
   299  		*assign = build
   300  
   301  		return nil
   302  	}
   303  }
   304  
   305  func (builder Builder) WithNextInputMapping(jobName string, inputs JobInputs) SetupFunc {
   306  	return func(scenario *Scenario) error {
   307  		if scenario.Pipeline == nil {
   308  			return fmt.Errorf("no pipeline set in scenario")
   309  		}
   310  
   311  		job, found, err := scenario.Pipeline.Job(jobName)
   312  		if err != nil {
   313  			return err
   314  		}
   315  
   316  		if !found {
   317  			return fmt.Errorf("job '%s' not configured in pipeline", jobName)
   318  		}
   319  
   320  		jobInputs, err := job.AlgorithmInputs()
   321  		if err != nil {
   322  			return fmt.Errorf("get job inputs: %w", err)
   323  		}
   324  
   325  		var hasErrors bool
   326  		mapping := db.InputMapping{}
   327  		for _, input := range jobInputs {
   328  			i, found := inputs.Lookup(input.Name)
   329  			if !found {
   330  				return fmt.Errorf("no version specified for input '%s'", input.Name)
   331  			}
   332  
   333  			buildIDs := []int{}
   334  			for _, build := range i.PassedBuilds {
   335  				buildIDs = append(buildIDs, build.ID())
   336  			}
   337  
   338  			mapping[input.Name] = db.InputResult{
   339  				Input: &db.AlgorithmInput{
   340  					AlgorithmVersion: db.AlgorithmVersion{
   341  						Version:    db.ResourceVersion(md5Version(i.Version)),
   342  						ResourceID: input.ResourceID,
   343  					},
   344  					FirstOccurrence: i.FirstOccurrence,
   345  				},
   346  				PassedBuildIDs: buildIDs,
   347  				ResolveError:   db.ResolutionFailure(i.ResolveError),
   348  			}
   349  
   350  			if i.ResolveError != "" {
   351  				hasErrors = true
   352  			}
   353  		}
   354  
   355  		err = job.SaveNextInputMapping(mapping, !hasErrors)
   356  		if err != nil {
   357  			return fmt.Errorf("save job input mapping: %w", err)
   358  		}
   359  
   360  		return nil
   361  	}
   362  }
   363  
   364  func (builder Builder) WithJobBuild(assign *db.Build, jobName string, inputs JobInputs, outputs JobOutputs) SetupFunc {
   365  	return func(scenario *Scenario) error {
   366  		var build db.Build
   367  		scenario.Run(
   368  			builder.WithPendingJobBuild(&build, jobName),
   369  			builder.WithNextInputMapping(jobName, inputs),
   370  		)
   371  
   372  		_, inputsReady, err := build.AdoptInputsAndPipes()
   373  		if err != nil {
   374  			return fmt.Errorf("adopt inputs and pipes: %w", err)
   375  		}
   376  
   377  		if !inputsReady {
   378  			return fmt.Errorf("inputs not available?")
   379  		}
   380  
   381  		job, found, err := scenario.Pipeline.Job(jobName)
   382  		if err != nil {
   383  			return err
   384  		}
   385  
   386  		if !found {
   387  			return fmt.Errorf("job '%s' not configured in pipeline", jobName)
   388  		}
   389  
   390  		resourceTypes, err := scenario.Pipeline.ResourceTypes()
   391  		if err != nil {
   392  			return fmt.Errorf("get pipeline resource types: %w", err)
   393  		}
   394  
   395  		jobOutputs, err := job.Outputs()
   396  		if err != nil {
   397  			return fmt.Errorf("get job outputs: %w", err)
   398  		}
   399  
   400  		for _, output := range jobOutputs {
   401  			version, found := outputs[output.Name]
   402  			if !found {
   403  				return fmt.Errorf("no version specified for output '%s'", output.Name)
   404  			}
   405  
   406  			resource, found, err := scenario.Pipeline.Resource(output.Resource)
   407  			if err != nil {
   408  				return fmt.Errorf("get output resource: %w", err)
   409  			}
   410  
   411  			if !found {
   412  				return fmt.Errorf("output '%s' refers to unknown resource '%s'", output.Name, output.Resource)
   413  			}
   414  
   415  			err = build.SaveOutput(
   416  				resource.Type(),
   417  				resource.Source(),
   418  				resourceTypes.Deserialize(),
   419  				version,
   420  				nil, // metadata
   421  				output.Name,
   422  				output.Resource,
   423  			)
   424  			if err != nil {
   425  				return fmt.Errorf("save build output: %w", err)
   426  			}
   427  		}
   428  
   429  		found, err = build.Reload()
   430  		if err != nil {
   431  			return fmt.Errorf("reload build: %w", err)
   432  		}
   433  
   434  		if !found {
   435  			return fmt.Errorf("build disappeared")
   436  		}
   437  
   438  		*assign = build
   439  
   440  		return nil
   441  	}
   442  }
   443  
   444  func (builder Builder) WithCheckContainer(resourceName string, workerName string) SetupFunc {
   445  	return func(scenario *Scenario) error {
   446  		if scenario.Pipeline == nil {
   447  			return fmt.Errorf("no pipeline set in scenario")
   448  		}
   449  
   450  		if len(scenario.Workers) == 0 {
   451  			return fmt.Errorf("no workers set in scenario")
   452  		}
   453  
   454  		resource, found, err := scenario.Pipeline.Resource(resourceName)
   455  		if err != nil {
   456  			return err
   457  		}
   458  
   459  		if !found {
   460  			return fmt.Errorf("resource '%s' not configured in pipeline", resourceName)
   461  		}
   462  
   463  		rc, found, err := builder.ResourceConfigFactory.FindResourceConfigByID(resource.ResourceConfigID())
   464  		if err != nil {
   465  			return err
   466  		}
   467  
   468  		if !found {
   469  			return fmt.Errorf("resource config '%d' not found", rc.ID())
   470  		}
   471  
   472  		owner := db.NewResourceConfigCheckSessionContainerOwner(
   473  			rc.ID(),
   474  			rc.OriginBaseResourceType().ID,
   475  			db.ContainerOwnerExpiries{
   476  				Min: 5 * time.Minute,
   477  				Max: 5 * time.Minute,
   478  			},
   479  		)
   480  
   481  		worker, found, err := builder.WorkerFactory.GetWorker(workerName)
   482  		if err != nil {
   483  			return err
   484  		}
   485  
   486  		if !found {
   487  			return fmt.Errorf("worker '%d' not set in the scenario", rc.ID())
   488  		}
   489  
   490  		containerMetadata := db.ContainerMetadata{
   491  			Type: "check",
   492  		}
   493  
   494  		_, err = worker.CreateContainer(owner, containerMetadata)
   495  		if err != nil {
   496  			return err
   497  		}
   498  
   499  		return nil
   500  	}
   501  }
   502  
   503  func (builder Builder) WithSpanContext(spanContext db.SpanContext) SetupFunc {
   504  	return func(scenario *Scenario) error {
   505  		scenario.SpanContext = spanContext
   506  		return nil
   507  	}
   508  }
   509  
   510  func (builder Builder) WithVersionMetadata(resourceName string, version atc.Version, metadata db.ResourceConfigMetadataFields) SetupFunc {
   511  	return func(scenario *Scenario) error {
   512  		resource, found, err := scenario.Pipeline.Resource(resourceName)
   513  		if err != nil {
   514  			return err
   515  		}
   516  
   517  		if !found {
   518  			return fmt.Errorf("resource '%s' not configured in pipeline", resourceName)
   519  		}
   520  
   521  		_, err = resource.UpdateMetadata(version, metadata)
   522  		if err != nil {
   523  			return err
   524  		}
   525  
   526  		return nil
   527  	}
   528  }
   529  
   530  func (builder Builder) WithPinnedVersion(resourceName string, pinnedVersion atc.Version) SetupFunc {
   531  	return func(scenario *Scenario) error {
   532  		resource, found, err := scenario.Pipeline.Resource(resourceName)
   533  		if err != nil {
   534  			return err
   535  		}
   536  
   537  		if !found {
   538  			return fmt.Errorf("resource '%s' not configured in pipeline", resourceName)
   539  		}
   540  
   541  		version, found, err := resource.FindVersion(pinnedVersion)
   542  		if err != nil {
   543  			return err
   544  		}
   545  
   546  		if !found {
   547  			scenario.Run(builder.WithResourceVersions(resourceName, pinnedVersion))
   548  
   549  			reloaded, err := resource.Reload()
   550  			if err != nil {
   551  				return err
   552  			}
   553  
   554  			if !reloaded {
   555  				return fmt.Errorf("resource '%s' not reloaded", resourceName)
   556  			}
   557  
   558  			version, found, err = resource.FindVersion(pinnedVersion)
   559  			if err != nil {
   560  				return err
   561  			}
   562  
   563  			if !found {
   564  				return fmt.Errorf("version '%v' not able to be saved", pinnedVersion)
   565  			}
   566  		}
   567  
   568  		_, err = resource.PinVersion(version.ID())
   569  		if err != nil {
   570  			return err
   571  		}
   572  
   573  		if !found {
   574  			return fmt.Errorf("version '%v' not pinned", version)
   575  		}
   576  
   577  		return nil
   578  	}
   579  }
   580  
   581  func (builder Builder) WithDisabledVersion(resourceName string, disabledVersion atc.Version) SetupFunc {
   582  	return func(scenario *Scenario) error {
   583  		resource, found, err := scenario.Pipeline.Resource(resourceName)
   584  		if err != nil {
   585  			return err
   586  		}
   587  
   588  		if !found {
   589  			return fmt.Errorf("resource '%s' not configured in pipeline", resourceName)
   590  		}
   591  
   592  		version, found, err := resource.FindVersion(disabledVersion)
   593  		if err != nil {
   594  			return err
   595  		}
   596  
   597  		if !found {
   598  			scenario.Run(builder.WithResourceVersions(resourceName, disabledVersion))
   599  
   600  			reloaded, err := resource.Reload()
   601  			if err != nil {
   602  				return err
   603  			}
   604  
   605  			if !reloaded {
   606  				return fmt.Errorf("resource '%s' not reloaded", resourceName)
   607  			}
   608  
   609  			version, found, err = resource.FindVersion(disabledVersion)
   610  			if err != nil {
   611  				return err
   612  			}
   613  
   614  			if !found {
   615  				return fmt.Errorf("version '%v' not able to be saved", disabledVersion)
   616  			}
   617  		}
   618  
   619  		err = resource.DisableVersion(version.ID())
   620  		if err != nil {
   621  			return err
   622  		}
   623  
   624  		return nil
   625  	}
   626  }
   627  
   628  func (builder Builder) WithEnabledVersion(resourceName string, enabledVersion atc.Version) SetupFunc {
   629  	return func(scenario *Scenario) error {
   630  		resource, found, err := scenario.Pipeline.Resource(resourceName)
   631  		if err != nil {
   632  			return err
   633  		}
   634  
   635  		if !found {
   636  			return fmt.Errorf("resource '%s' not configured in pipeline", resourceName)
   637  		}
   638  
   639  		version, found, err := resource.FindVersion(enabledVersion)
   640  		if err != nil {
   641  			return err
   642  		}
   643  
   644  		if found {
   645  			err = resource.EnableVersion(version.ID())
   646  			if err != nil {
   647  				return err
   648  			}
   649  		} else {
   650  			scenario.Run(builder.WithResourceVersions(resourceName, enabledVersion))
   651  		}
   652  
   653  		return nil
   654  	}
   655  }
   656  
   657  func (builder Builder) WithBaseResourceType(dbConn db.Conn, resourceTypeName string) SetupFunc {
   658  	return func(scenario *Scenario) error {
   659  		setupTx, err := dbConn.Begin()
   660  		if err != nil {
   661  			return err
   662  		}
   663  
   664  		brt := db.BaseResourceType{
   665  			Name: resourceTypeName,
   666  		}
   667  
   668  		_, err = brt.FindOrCreate(setupTx, false)
   669  		if err != nil {
   670  			return err
   671  		}
   672  
   673  		return setupTx.Commit()
   674  	}
   675  }
   676  
   677  func unique(kind string) string {
   678  	id, err := uuid.NewV4()
   679  	if err != nil {
   680  		panic(err)
   681  	}
   682  
   683  	return kind + "-" + id.String()
   684  }
   685  
   686  func md5Version(version atc.Version) string {
   687  	versionJSON, err := json.Marshal(version)
   688  	if err != nil {
   689  		panic(err)
   690  	}
   691  
   692  	hasher := md5.New()
   693  	hasher.Write([]byte(versionJSON))
   694  	return hex.EncodeToString(hasher.Sum(nil))
   695  }