Ejemplo n.º 1
0
func deploy(app provision.App, version string, w io.Writer) (string, error) {
	commands, err := deployCmds(app, version)
	if err != nil {
		return "", err
	}
	imageId := getImage(app)
	actions := []*action.Action{&insertEmptyContainerInDB, &createContainer, &startContainer, &updateContainerInDB}
	pipeline := action.NewPipeline(actions...)
	err = pipeline.Execute(app, imageId, commands)
	if err != nil {
		log.Errorf("error on execute deploy pipeline for app %s - %s", app.GetName(), err)
		return "", err
	}
	c := pipeline.Result().(container)
	err = c.logs(w)
	if err != nil {
		log.Errorf("error on get logs for container %s - %s", c.ID, err)
		return "", err
	}
	_, err = dockerCluster().WaitContainer(c.ID)
	if err != nil {
		log.Errorf("Process failed for container %q: %s", c.ID, err)
		return "", err
	}
	imageId, err = c.commit()
	if err != nil {
		log.Errorf("error on commit container %s - %s", c.ID, err)
		return "", err
	}
	c.remove()
	return imageId, nil
}
Ejemplo n.º 2
0
// CreateApp creates a new app.
//
// Creating a new app is a process composed of the following steps:
//
//       1. Save the app in the database
//       2. Create the git repository using the repository manager
//       3. Provision the app using the provisioner
func CreateApp(app *App, user *auth.User) error {
	teams, err := user.Teams()
	if err != nil {
		return err
	}
	if len(teams) == 0 {
		return NoTeamsError{}
	}
	platform, err := getPlatform(app.Platform)
	if err != nil {
		return err
	}
	if platform.Disabled && !user.IsAdmin() {
		return InvalidPlatformError{}
	}
	var plan *Plan
	if app.Plan.Name == "" {
		plan, err = DefaultPlan()
	} else {
		plan, err = findPlanByName(app.Plan.Name)
	}
	if err != nil {
		return err
	}
	if app.TeamOwner == "" {
		if len(teams) > 1 {
			return ManyTeamsError{}
		}
		app.TeamOwner = teams[0].Name
	}
	err = app.ValidateTeamOwner(user)
	if err != nil {
		return err
	}
	app.Plan = *plan
	err = app.SetPool()
	if err != nil {
		return err
	}
	app.Teams = []string{app.TeamOwner}
	app.Owner = user.Email
	err = app.validate()
	if err != nil {
		return err
	}
	actions := []*action.Action{
		&reserveUserApp,
		&insertApp,
		&exportEnvironmentsAction,
		&createRepository,
		&provisionApp,
		&setAppIp,
	}
	pipeline := action.NewPipeline(actions...)
	err = pipeline.Execute(app, user)
	if err != nil {
		return &AppCreationError{app: app.Name, Err: err}
	}
	return nil
}
Ejemplo n.º 3
0
func addUserToTeam(w http.ResponseWriter, r *http.Request, t auth.Token) error {
	teamName := r.URL.Query().Get(":team")
	email := r.URL.Query().Get(":user")
	u, err := t.User()
	if err != nil {
		return err
	}
	rec.Log(u.Email, "add-user-to-team", "team="+teamName, "user="+email)
	conn, err := db.Conn()
	if err != nil {
		return err
	}
	defer conn.Close()
	team, err := auth.GetTeam(teamName)
	if err != nil {
		return &errors.HTTP{Code: http.StatusNotFound, Message: "Team not found"}
	}
	if !team.ContainsUser(u) {
		msg := fmt.Sprintf("You are not authorized to add new users to the team %s", team.Name)
		return &errors.HTTP{Code: http.StatusForbidden, Message: msg}
	}
	user, err := auth.GetUserByEmail(email)
	if err != nil {
		return &errors.HTTP{Code: http.StatusNotFound, Message: "User not found"}
	}
	actions := []*action.Action{
		&addUserToTeamInRepositoryAction,
		&addUserToTeamInDatabaseAction,
	}
	pipeline := action.NewPipeline(actions...)
	return pipeline.Execute(user, team)
}
Ejemplo n.º 4
0
func moveOneContainer(c container, toHost string, errors chan error, wg *sync.WaitGroup, encoder *json.Encoder) {
	a, err := app.GetByName(c.AppName)
	defer wg.Done()
	if err != nil {
		errors <- err
		return
	}
	logProgress(encoder, "Moving unit %s for %q: %s -> %s...", c.ID, c.AppName, c.HostAddr, toHost)
	pipeline := action.NewPipeline(
		&provisionAddUnitToHost,
		&provisionRemoveOldUnit,
	)
	err = pipeline.Execute(a, toHost, c)
	if err != nil {
		errors <- err
		return
	}
	logProgress(encoder, "Finished moving unit %s for %q.", c.ID, c.AppName)
	addedUnit := pipeline.Result().(provision.Unit)
	err = moveOneContainerInDB(a, c, addedUnit)
	if err != nil {
		errors <- err
		return
	}
	logProgress(encoder, "Moved unit %s -> %s for %s in DB.", c.ID, addedUnit.Name, c.AppName)
}
Ejemplo n.º 5
0
Archivo: docker.go Proyecto: 4eek/tsuru
func (p *dockerProvisioner) deployPipeline(app provision.App, imageId string, commands []string, w io.Writer) (string, error) {
	actions := []*action.Action{
		&insertEmptyContainerInDB,
		&createContainer,
		&startContainer,
		&updateContainerInDB,
		&followLogsAndCommit,
	}
	pipeline := action.NewPipeline(actions...)
	buildingImage, err := appNewImageName(app.GetName())
	if err != nil {
		return "", log.WrapError(fmt.Errorf("error getting new image name for app %s", app.GetName()))
	}
	args := runContainerActionsArgs{
		app:           app,
		imageID:       imageId,
		commands:      commands,
		writer:        w,
		isDeploy:      true,
		buildingImage: buildingImage,
		provisioner:   p,
	}
	err = pipeline.Execute(args)
	if err != nil {
		log.Errorf("error on execute deploy pipeline for app %s - %s", app.GetName(), err)
		return "", err
	}
	return buildingImage, nil
}
// BindApp makes the bind between the service instance and an app.
func (si *ServiceInstance) BindApp(app bind.App) error {
	actions := []*action.Action{
		&addAppToServiceInstance,
		&setEnvironVariablesToApp,
	}
	pipeline := action.NewPipeline(actions...)
	return pipeline.Execute(app, *si)
}
Ejemplo n.º 7
0
func (p *dockerProvisioner) DeployPipeline() *action.Pipeline {
	actions := []*action.Action{
		&app.ProvisionerDeploy,
		&app.IncrementDeploy,
		&app.BindService,
	}
	pipeline := action.NewPipeline(actions...)
	return pipeline
}
Ejemplo n.º 8
0
func (s *S) TestRebalanceContainersDry(c *check.C) {
	p, err := s.startMultipleServersCluster()
	c.Assert(err, check.IsNil)
	err = s.newFakeImage(p, "tsuru/app-myapp", nil)
	c.Assert(err, check.IsNil)
	appInstance := provisiontest.NewFakeApp("myapp", "python", 0)
	defer p.Destroy(appInstance)
	p.Provision(appInstance)
	imageId, err := image.AppCurrentImageName(appInstance.GetName())
	c.Assert(err, check.IsNil)
	args := changeUnitsPipelineArgs{
		app:         appInstance,
		toAdd:       map[string]*containersToAdd{"web": {Quantity: 5}},
		imageId:     imageId,
		provisioner: p,
		toHost:      "localhost",
	}
	pipeline := action.NewPipeline(
		&provisionAddUnitsToHost,
		&bindAndHealthcheck,
		&addNewRoutes,
		&setRouterHealthcheck,
		&updateAppImage,
	)
	err = pipeline.Execute(args)
	c.Assert(err, check.IsNil)
	appStruct := &app.App{
		Name: appInstance.GetName(),
		Pool: "test-default",
	}
	err = s.storage.Apps().Insert(appStruct)
	c.Assert(err, check.IsNil)
	router, err := getRouterForApp(appInstance)
	c.Assert(err, check.IsNil)
	beforeRoutes, err := router.Routes(appStruct.Name)
	c.Assert(err, check.IsNil)
	c.Assert(beforeRoutes, check.HasLen, 5)
	var serviceCalled bool
	rollback := s.addServiceInstance(c, appInstance.GetName(), nil, func(w http.ResponseWriter, r *http.Request) {
		serviceCalled = true
		w.WriteHeader(http.StatusOK)
	})
	defer rollback()
	buf := safe.NewBuffer(nil)
	err = p.rebalanceContainers(buf, true)
	c.Assert(err, check.IsNil)
	c1, err := p.listContainersByHost("localhost")
	c.Assert(err, check.IsNil)
	c2, err := p.listContainersByHost("127.0.0.1")
	c.Assert(err, check.IsNil)
	c.Assert(c1, check.HasLen, 5)
	c.Assert(c2, check.HasLen, 0)
	routes, err := router.Routes(appStruct.Name)
	c.Assert(err, check.IsNil)
	c.Assert(routes, check.DeepEquals, beforeRoutes)
	c.Assert(serviceCalled, check.Equals, false)
}
Ejemplo n.º 9
0
// BindApp makes the bind between the service instance and an app.
func (si *ServiceInstance) BindApp(app bind.App, writer io.Writer) error {
	actions := []*action.Action{
		&addAppToServiceInstance,
		&setBindAppAction,
		&setTsuruServices,
		&bindUnitsToServiceInstance,
	}
	pipeline := action.NewPipeline(actions...)
	return pipeline.Execute(app, *si, writer)
}
Ejemplo n.º 10
0
// AddUnits creates n new units within the provisioner, saves new units in the
// database and enqueues the apprc serialization.
func (app *App) AddUnits(n uint, process string, writer io.Writer) error {
	if n == 0 {
		return stderr.New("Cannot add zero units.")
	}
	err := action.NewPipeline(
		&reserveUnitsToAdd,
		&provisionAddUnits,
	).Execute(app, n, writer, process)
	return err
}
Ejemplo n.º 11
0
Archivo: app.go Proyecto: tsuru/tsuru
// AddCName adds a CName to app. It updates the attribute,
// calls the SetCName function on the provisioner and saves
// the app in the database, returning an error when it cannot save the change
// in the database or add the CName on the provisioner.
func (app *App) AddCName(cnames ...string) error {
	actions := []*action.Action{
		&validateNewCNames,
		&setNewCNamesToProvisioner,
		&saveCNames,
		&updateApp,
	}
	err := action.NewPipeline(actions...).Execute(app, cnames)
	rebuild.RoutesRebuildOrEnqueue(app.Name)
	return err
}
Ejemplo n.º 12
0
func deploy(app provision.App, commands []string, w io.Writer) (string, error) {
	imageId := getImage(app)
	actions := []*action.Action{&insertEmptyContainerInDB, &createContainer, &startContainer, &updateContainerInDB, &followLogsAndCommit}
	pipeline := action.NewPipeline(actions...)
	err := pipeline.Execute(app, imageId, commands, []string{}, w)
	if err != nil {
		log.Errorf("error on execute deploy pipeline for app %s - %s", app.GetName(), err)
		return "", err
	}
	return pipeline.Result().(string), nil
}
Ejemplo n.º 13
0
Archivo: app.go Proyecto: tsuru/tsuru
func (app *App) RemoveCName(cnames ...string) error {
	actions := []*action.Action{
		&checkCNameExists,
		&unsetCNameFromProvisioner,
		&removeCNameFromDatabase,
		&removeCNameFromApp,
	}
	err := action.NewPipeline(actions...).Execute(app, cnames)
	rebuild.RoutesRebuildOrEnqueue(app.Name)
	return err
}
Ejemplo n.º 14
0
// AddUnits creates n new units within the provisioner, saves new units in the
// database and enqueues the apprc serialization.
func (app *App) AddUnits(n uint) error {
	if n == 0 {
		return stderr.New("Cannot add zero units.")
	}
	err := action.NewPipeline(
		&reserveUnitsToAdd,
		&provisionAddUnits,
		&saveNewUnitsInDatabase,
	).Execute(app, n)
	return err
}
Ejemplo n.º 15
0
func (p *dockerProvisioner) DeployPipeline() *action.Pipeline {
	actions := []*action.Action{
		&app.ProvisionerDeploy,
		&app.IncrementDeploy,
		//&saveUnits,
		&injectEnvirons,
		&bindService,
	}
	pipeline := action.NewPipeline(actions...)
	return pipeline
}
Ejemplo n.º 16
0
func (p *dockerProvisioner) Destroy(app provision.App) error {
	containers, err := p.listContainersByApp(app.GetName())
	if err != nil {
		log.Errorf("Failed to list app containers: %s", err.Error())
		return err
	}
	args := changeUnitsPipelineArgs{
		app:         app,
		toRemove:    containers,
		writer:      ioutil.Discard,
		provisioner: p,
		appDestroy:  true,
	}
	pipeline := action.NewPipeline(
		&removeOldRoutes,
		&provisionRemoveOldUnits,
		&provisionUnbindOldUnits,
	)
	err = pipeline.Execute(args)
	if err != nil {
		return err
	}
	images, err := listAppImages(app.GetName())
	if err != nil {
		log.Errorf("Failed to get image ids for app %s: %s", app.GetName(), err.Error())
	}
	cluster := p.Cluster()
	for _, imageId := range images {
		err = cluster.RemoveImage(imageId)
		if err != nil {
			log.Errorf("Failed to remove image %s: %s", imageId, err.Error())
		}
		err = cluster.RemoveFromRegistry(imageId)
		if err != nil {
			log.Errorf("Failed to remove image %s from registry: %s", imageId, err.Error())
		}
	}
	err = deleteAllAppImageNames(app.GetName())
	if err != nil {
		log.Errorf("Failed to remove image names from storage for app %s: %s", app.GetName(), err.Error())
	}
	r, err := getRouterForApp(app)
	if err != nil {
		log.Errorf("Failed to get router: %s", err.Error())
		return err
	}
	err = r.RemoveBackend(app.GetName())
	if err != nil {
		log.Errorf("Failed to remove route backend: %s", err.Error())
		return err
	}
	return nil
}
Ejemplo n.º 17
0
func (p *dockerProvisioner) runReplaceUnitsPipeline(w io.Writer, a provision.App, toAdd map[string]*containersToAdd, toRemoveContainers []container.Container, imageId string, toHosts ...string) ([]container.Container, error) {
	var toHost string
	if len(toHosts) > 0 {
		toHost = toHosts[0]
	}
	if w == nil {
		w = ioutil.Discard
	}
	args := changeUnitsPipelineArgs{
		app:         a,
		toAdd:       toAdd,
		toRemove:    toRemoveContainers,
		toHost:      toHost,
		writer:      w,
		imageId:     imageId,
		provisioner: p,
	}
	var pipeline *action.Pipeline
	if p.isDryMode {
		pipeline = action.NewPipeline(
			&provisionAddUnitsToHost,
			&provisionRemoveOldUnits,
		)
	} else {
		pipeline = action.NewPipeline(
			&provisionAddUnitsToHost,
			&bindAndHealthcheck,
			&addNewRoutes,
			&removeOldRoutes,
			&updateAppImage,
			&provisionRemoveOldUnits,
			&provisionUnbindOldUnits,
		)
	}
	err := pipeline.Execute(args)
	if err != nil {
		return nil, err
	}
	return pipeline.Result().([]container.Container), nil
}
Ejemplo n.º 18
0
Archivo: app.go Proyecto: botvs/tsuru
// Update changes informations of the application.
func (app *App) Update(updateData App, w io.Writer) error {
	description := updateData.Description
	planName := updateData.Plan.Name
	poolName := updateData.Pool
	teamOwner := updateData.TeamOwner
	if description != "" {
		app.Description = description
	}
	if poolName != "" {
		app.Pool = poolName
		_, err := app.GetPoolForApp(app.Pool)
		if err != nil {
			return err
		}
	}
	conn, err := db.Conn()
	if err != nil {
		return err
	}
	defer conn.Close()
	if planName != "" {
		plan, err := findPlanByName(planName)
		if err != nil {
			return err
		}
		var oldPlan Plan
		oldPlan, app.Plan = app.Plan, *plan
		actions := []*action.Action{
			&moveRouterUnits,
			&saveApp,
			&restartApp,
			&removeOldBackend,
		}
		err = action.NewPipeline(actions...).Execute(app, &oldPlan, w)
		if err != nil {
			return err
		}
	}
	if teamOwner != "" {
		team, err := auth.GetTeam(teamOwner)
		if err != nil {
			return err
		}
		app.TeamOwner = team.Name
		err = app.validateTeamOwner()
		if err != nil {
			return err
		}
		app.Grant(team)
	}
	return conn.Apps().Update(bson.M{"name": app.Name}, app)
}
Ejemplo n.º 19
0
func (p *PipelineErrorFakeProvisioner) DeployPipeline() *action.Pipeline {
	act := action.Action{
		Name: "error-pipeline",
		Forward: func(ctx action.FWContext) (action.Result, error) {
			return nil, errors.New("deploy error")
		},
		Backward: func(ctx action.BWContext) {
		},
	}
	actions := []*action.Action{&act}
	pipeline := action.NewPipeline(actions...)
	return pipeline
}
Ejemplo n.º 20
0
func CreateServiceInstance(instance ServiceInstance, service *Service, user *auth.User) error {
	err := validateServiceInstanceName(service.Name, instance.Name)
	if err != nil {
		return err
	}
	instance.ServiceName = service.Name
	if instance.TeamOwner == "" {
		return ErrTeamMandatory
	}
	instance.Teams = []string{instance.TeamOwner}
	actions := []*action.Action{&createServiceInstance, &insertServiceInstance}
	pipeline := action.NewPipeline(actions...)
	return pipeline.Execute(*service, instance, user.Email)
}
Ejemplo n.º 21
0
func (p *PipelineFakeProvisioner) DeployPipeline() *action.Pipeline {
	act := action.Action{
		Name: "change-executed-pipeline",
		Forward: func(ctx action.FWContext) (action.Result, error) {
			p.executedPipeline = true
			return nil, nil
		},
		Backward: func(ctx action.BWContext) {
		},
	}
	actions := []*action.Action{&act}
	pipeline := action.NewPipeline(actions...)
	return pipeline
}
Ejemplo n.º 22
0
// BindApp makes the bind between the service instance and an app.
func (si *ServiceInstance) BindApp(app bind.App, writer io.Writer) error {
	args := bindPipelineArgs{
		serviceInstance: si,
		app:             app,
		writer:          writer,
	}
	actions := []*action.Action{
		&bindAppDBAction,
		&bindAppEndpointAction,
		&setBindedEnvsAction,
		&bindUnitsAction,
	}
	pipeline := action.NewPipeline(actions...)
	return pipeline.Execute(&args)
}
Ejemplo n.º 23
0
// ChangePlan changes the plan of the application.
//
// It may change the state of the application if the new plan includes a new
// router or a change in the amount of available memory.
func (app *App) ChangePlan(planName string, w io.Writer) error {
	plan, err := findPlanByName(planName)
	if err != nil {
		return err
	}
	var oldPlan Plan
	oldPlan, app.Plan = app.Plan, *plan
	actions := []*action.Action{
		&moveRouterUnits,
		&saveApp,
		&restartApp,
		&removeOldBackend,
	}
	return action.NewPipeline(actions...).Execute(app, &oldPlan, w)
}
Ejemplo n.º 24
0
// DeployApp calls the Provisioner.Deploy
func DeployApp(app *App, version, commit string, writer io.Writer) error {
	start := time.Now()
	pipeline := Provisioner.DeployPipeline()
	if pipeline == nil {
		actions := []*action.Action{&ProvisionerDeploy, &IncrementDeploy}
		pipeline = action.NewPipeline(actions...)
	}
	logWriter := LogWriter{App: app, Writer: writer}
	err := pipeline.Execute(app, version, &logWriter)
	if err != nil {
		return err
	}
	elapsed := time.Since(start)
	return saveDeployData(app.Name, commit, elapsed)
}
Ejemplo n.º 25
0
// BindApp makes the bind between the service instance and an app.
func (si *ServiceInstance) BindApp(app bind.App, shouldRestart bool, writer io.Writer) error {
	args := bindPipelineArgs{
		serviceInstance: si,
		app:             app,
		writer:          writer,
		shouldRestart:   shouldRestart,
	}
	actions := []*action.Action{
		bindAppDBAction,
		bindAppEndpointAction,
		setBoundEnvsAction,
		bindUnitsAction,
	}
	pipeline := action.NewPipeline(actions...)
	return pipeline.Execute(&args)
}
Ejemplo n.º 26
0
func runCreateUnitsPipeline(w io.Writer, a provision.App, toAddCount int) ([]container, error) {
	args := changeUnitsPipelineArgs{
		app:        a,
		unitsToAdd: toAddCount,
		writer:     w,
	}
	pipeline := action.NewPipeline(
		&provisionAddUnitsToHost,
		&addNewRoutes,
	)
	err := pipeline.Execute(args)
	if err != nil {
		return nil, err
	}
	return pipeline.Result().([]container), nil
}
Ejemplo n.º 27
0
func (p *dockerProvisioner) start(oldContainer *container.Container, app provision.App, imageId string, w io.Writer, exposedPort string, destinationHosts ...string) (*container.Container, error) {
	commands, processName, err := dockercommon.LeanContainerCmds(oldContainer.ProcessName, imageId, app)
	if err != nil {
		return nil, err
	}
	var actions []*action.Action
	if oldContainer != nil && oldContainer.Status == provision.StatusStopped.String() {
		actions = []*action.Action{
			&insertEmptyContainerInDB,
			&createContainer,
			&setContainerID,
			&stopContainer,
			&updateContainerInDB,
			&setNetworkInfo,
		}
	} else {
		actions = []*action.Action{
			&insertEmptyContainerInDB,
			&createContainer,
			&setContainerID,
			&startContainer,
			&updateContainerInDB,
			&setNetworkInfo,
		}
	}
	pipeline := action.NewPipeline(actions...)
	args := runContainerActionsArgs{
		app:              app,
		processName:      processName,
		imageID:          imageId,
		commands:         commands,
		destinationHosts: destinationHosts,
		provisioner:      p,
		exposedPort:      exposedPort,
	}
	err = pipeline.Execute(args)
	if err != nil {
		return nil, err
	}
	c := pipeline.Result().(container.Container)
	err = c.SetImage(p, imageId)
	if err != nil {
		return nil, err
	}
	return &c, nil
}
Ejemplo n.º 28
0
// UnbindApp makes the unbind between the service instance and an app.
func (si *ServiceInstance) UnbindApp(app bind.App, writer io.Writer) error {
	if si.FindApp(app.GetName()) == -1 {
		return &errors.HTTP{Code: http.StatusPreconditionFailed, Message: "This app is not bound to this service instance."}
	}
	args := bindPipelineArgs{
		serviceInstance: si,
		app:             app,
		writer:          writer,
	}
	actions := []*action.Action{
		&unbindUnits,
		&unbindAppDB,
		&unbindAppEndpoint,
		&removeBindedEnvs,
	}
	pipeline := action.NewPipeline(actions...)
	return pipeline.Execute(&args)
}
Ejemplo n.º 29
0
// UnbindApp makes the unbind between the service instance and an app.
func (si *ServiceInstance) UnbindApp(app bind.App, shouldRestart bool, writer io.Writer) error {
	if si.FindApp(app.GetName()) == -1 {
		return ErrAppNotBound
	}
	args := bindPipelineArgs{
		serviceInstance: si,
		app:             app,
		writer:          writer,
		shouldRestart:   shouldRestart,
	}
	actions := []*action.Action{
		&unbindUnits,
		&unbindAppDB,
		&unbindAppEndpoint,
		&removeBoundEnvs,
	}
	pipeline := action.NewPipeline(actions...)
	return pipeline.Execute(&args)
}
Ejemplo n.º 30
0
func start(app provision.App, imageId string, w io.Writer, destinationHosts ...string) (*container, error) {
	keyPair, err := rsa.GenerateKey(rand.Reader, 1024)
	if err != nil {
		return nil, err
	}
	privateKey, publicKey, err := marshalKey(keyPair)
	if err != nil {
		return nil, err
	}
	commands, err := runWithAgentCmds(app, publicKey)
	if err != nil {
		return nil, err
	}
	actions := []*action.Action{
		&insertEmptyContainerInDB,
		&createContainer,
		&startContainer,
		&updateContainerInDB,
		&setNetworkInfo,
	}
	pipeline := action.NewPipeline(actions...)
	args := runContainerActionsArgs{
		app:              app,
		imageID:          imageId,
		commands:         commands,
		destinationHosts: destinationHosts,
		privateKey:       privateKey,
	}
	err = pipeline.Execute(args)
	if err != nil {
		return nil, err
	}
	c := pipeline.Result().(container)
	err = c.setImage(imageId)
	if err != nil {
		return nil, err
	}
	err = c.setStatus(provision.StatusStarted.String())
	if err != nil {
		return nil, err
	}
	return &c, nil
}