Esempio n. 1
0
// servePutLeader sets the leader for a service.
func (h *Handler) servePutLeader(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
	// Retrieve path parameters.
	service := params.ByName("service")

	// Check if the service allows manual leader election.
	config := h.Store.Config(service)
	if config == nil || config.LeaderType != discoverd.LeaderTypeManual {
		hh.ValidationError(w, "", "service leader election type is not manual")
		return
	}

	// Read instance from the request.
	inst := &discoverd.Instance{}
	if err := hh.DecodeJSON(r, inst); err != nil {
		hh.Error(w, err)
		return
	}

	// Manually set the leader on the service.
	if err := h.Store.SetServiceLeader(service, inst.ID); err == ErrNotLeader {
		h.redirectToLeader(w, r)
		return
	} else if err != nil {
		hh.Error(w, err)
		return
	}
}
Esempio n. 2
0
func (h *jobAPI) Update(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	log := h.host.log.New("fn", "Update")

	log.Info("decoding command")
	var cmd host.Command
	if err := httphelper.DecodeJSON(req, &cmd); err != nil {
		log.Error("error decoding command", "err", err)
		httphelper.Error(w, err)
		return
	}

	log.Info("updating host")
	err := h.host.Update(&cmd)
	if err != nil {
		httphelper.Error(w, err)
		return
	}

	// send an ok response and then shutdown after 1s to give the response
	// chance to reach the client.
	httphelper.JSON(w, http.StatusOK, cmd)
	log.Info("shutting down in 1s")
	time.AfterFunc(time.Second, func() {
		log.Info("exiting")
		os.Exit(0)
	})
}
Esempio n. 3
0
func (h *jobAPI) ResourceCheck(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	var req host.ResourceCheck
	if err := httphelper.DecodeJSON(r, &req); err != nil {
		httphelper.Error(w, err)
		return
	}
	var conflicts []host.Port
	for _, p := range req.Ports {
		if p.Proto == "" {
			p.Proto = "tcp"
		}
		if !checkPort(p) {
			conflicts = append(conflicts, p)
		}
	}
	if len(conflicts) > 0 {
		resp := host.ResourceCheck{Ports: conflicts}
		detail, err := json.Marshal(resp)
		if err != nil {
			httphelper.Error(w, err)
			return
		}
		httphelper.JSON(w, 409, &httphelper.JSONError{
			Code:    httphelper.ConflictErrorCode,
			Message: "Conflicting resources found",
			Detail:  detail,
		})
		return
	}
	httphelper.JSON(w, 200, struct{}{})
}
Esempio n. 4
0
File: http.go Progetto: devick/flynn
func (api *HTTPAPI) Pull(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
	volumeID := ps.ByName("volume_id")

	pull := &volume.PullCoordinate{}
	if err := httphelper.DecodeJSON(r, &pull); err != nil {
		httphelper.Error(w, err)
		return
	}

	hostClient, err := api.cluster.Host(pull.HostID)
	if err != nil {
		httphelper.Error(w, err)
		return
	}

	haves, err := api.vman.ListHaves(volumeID)
	if err != nil {
		httphelper.Error(w, err)
		return
	}

	reader, err := hostClient.SendSnapshot(pull.SnapshotID, haves)
	if err != nil {
		httphelper.Error(w, err)
		return
	}

	snap, err := api.vman.ReceiveSnapshot(volumeID, reader)
	if err != nil {
		httphelper.Error(w, err)
		return
	}

	httphelper.JSON(w, 200, snap.Info())
}
Esempio n. 5
0
// servePutService creates a service.
func (h *Handler) servePutService(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
	// Retrieve the path parameter.
	service := params.ByName("service")
	if err := ValidServiceName(service); err != nil {
		hh.ValidationError(w, "", err.Error())
		return
	}

	// Read config from the request.
	config := &discoverd.ServiceConfig{}
	if err := hh.DecodeJSON(r, config); err != nil {
		hh.Error(w, err)
		return
	}

	// Add the service to the store.
	if err := h.Store.AddService(service, config); err == ErrNotLeader {
		h.redirectToLeader(w, r)
		return
	} else if IsServiceExists(err) {
		hh.ObjectExistsError(w, err.Error())
		return
	} else if err != nil {
		hh.Error(w, err)
		return
	}
}
Esempio n. 6
0
func (c *controllerAPI) PutResource(ctx context.Context, w http.ResponseWriter, req *http.Request) {
	params, _ := ctxhelper.ParamsFromContext(ctx)

	p, err := c.getProvider(ctx)
	if err != nil {
		respondWithError(w, err)
		return
	}

	var resource ct.Resource
	if err = httphelper.DecodeJSON(req, &resource); err != nil {
		respondWithError(w, err)
		return
	}

	resource.ID = params.ByName("resources_id")
	resource.ProviderID = p.ID

	if err := schema.Validate(resource); err != nil {
		respondWithError(w, err)
		return
	}

	if err := c.resourceRepo.Add(&resource); err != nil {
		respondWithError(w, err)
		return
	}
	httphelper.JSON(w, 200, &resource)
}
Esempio n. 7
0
func (api *httpAPI) NewCredential(w http.ResponseWriter, req *http.Request, params httprouter.Params) {
	creds := &Credential{}
	if err := httphelper.DecodeJSON(req, &creds); err != nil {
		httphelper.Error(w, err)
		return
	}
	if creds.Type == "azure" {
		oauthCreds := make([]*OAuthCredential, 0, 2)
		for _, resource := range []string{azure.JSONAPIResource, azure.XMLAPIResource} {
			token, err := azure.OAuth2Config(creds.ID, creds.Endpoint, resource).Exchange(oauth2.NoContext, creds.Secret)
			if err != nil {
				httphelper.Error(w, err)
				return
			}
			oauthCreds = append(oauthCreds, &OAuthCredential{
				ClientID:     creds.ID,
				AccessToken:  token.AccessToken,
				RefreshToken: token.RefreshToken,
				ExpiresAt:    &token.Expiry,
				Scope:        resource,
			})
		}
		creds.Secret = ""
		creds.OAuthCreds = oauthCreds
	}
	if err := api.Installer.SaveCredentials(creds); err != nil {
		if err == credentialExistsError {
			httphelper.ObjectExistsError(w, err.Error())
			return
		}
		httphelper.Error(w, err)
		return
	}
	w.WriteHeader(200)
}
Esempio n. 8
0
func (c *controllerAPI) PutFormation(ctx context.Context, w http.ResponseWriter, req *http.Request) {
	app := c.getApp(ctx)
	release, err := c.getRelease(ctx)
	if err != nil {
		respondWithError(w, err)
		return
	}

	var formation ct.Formation
	if err = httphelper.DecodeJSON(req, &formation); err != nil {
		respondWithError(w, err)
		return
	}

	if release.ArtifactID == "" {
		respondWithError(w, ct.ValidationError{Message: "release is not deployable"})
		return
	}

	formation.AppID = app.ID
	formation.ReleaseID = release.ID

	if err = schema.Validate(formation); err != nil {
		respondWithError(w, err)
		return
	}

	if err = c.formationRepo.Add(&formation); err != nil {
		respondWithError(w, err)
		return
	}
	httphelper.JSON(w, 200, &formation)
}
Esempio n. 9
0
File: http.go Progetto: devick/flynn
func (h *jobAPI) ConfigureNetworking(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	log := h.host.log.New("fn", "ConfigureNetworking")

	log.Info("decoding config")
	config := &host.NetworkConfig{}
	if err := httphelper.DecodeJSON(r, config); err != nil {
		log.Error("error decoding config", "err", err)
		shutdown.Fatal(err)
	}

	// configure the network before returning a response in case the
	// network coordinator requires the bridge to be created (e.g.
	// when using flannel with the "alloc" backend)
	h.host.networkOnce.Do(func() {
		log.Info("configuring network", "subnet", config.Subnet, "mtu", config.MTU, "resolvers", config.Resolvers)
		if err := h.host.backend.ConfigureNetworking(config); err != nil {
			log.Error("error configuring network", "err", err)
			shutdown.Fatal(err)
		}

		h.host.statusMtx.Lock()
		h.host.status.Network = config
		h.host.statusMtx.Unlock()
	})
}
Esempio n. 10
0
func (api *HTTPAPI) CreateProvider(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
	pspec := &volume.ProviderSpec{}
	if err := httphelper.DecodeJSON(r, &pspec); err != nil {
		httphelper.Error(w, err)
		return
	}
	if pspec.ID == "" {
		pspec.ID = random.UUID()
	}
	if pspec.Kind == "" {
		httphelper.ValidationError(w, "kind", "must not be blank")
		return
	}
	var provider volume.Provider
	provider, err := volumemanager.NewProvider(pspec)
	if err == volume.UnknownProviderKind {
		httphelper.ValidationError(w, "kind", fmt.Sprintf("%q is not known", pspec.Kind))
		return
	}

	if err := api.vman.AddProvider(pspec.ID, provider); err != nil {
		switch err {
		case volumemanager.ErrProviderExists:
			httphelper.ObjectExistsError(w, fmt.Sprintf("provider %q already exists", pspec.ID))
			return
		default:
			httphelper.Error(w, err)
			return
		}
	}

	httphelper.JSON(w, 200, pspec)
}
Esempio n. 11
0
func (api *HTTPAPI) Send(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
	volumeID := ps.ByName("volume_id")

	if !strings.Contains(r.Header.Get("Accept"), snapshotContentType) {
		httphelper.ValidationError(w, "", fmt.Sprintf("must be prepared to accept a content type of %q", snapshotContentType))
		return
	}
	w.Header().Set("Content-Type", snapshotContentType)

	var haves []json.RawMessage
	if err := httphelper.DecodeJSON(r, &haves); err != nil {
		httphelper.Error(w, err)
		return
	}

	err := api.vman.SendSnapshot(volumeID, haves, w)
	if err != nil {
		switch err {
		case volume.ErrNoSuchVolume:
			httphelper.ObjectNotFoundError(w, fmt.Sprintf("no volume with id %q", volumeID))
			return
		default:
			httphelper.Error(w, err)
			return
		}
	}
}
Esempio n. 12
0
func (c *controllerAPI) SetAppRelease(ctx context.Context, w http.ResponseWriter, req *http.Request) {
	var rid releaseID
	if err := httphelper.DecodeJSON(req, &rid); err != nil {
		respondWithError(w, err)
		return
	}

	rel, err := c.releaseRepo.Get(rid.ID)
	if err != nil {
		if err == ErrNotFound {
			err = ct.ValidationError{
				Message: fmt.Sprintf("could not find release with ID %s", rid.ID),
			}
		}
		respondWithError(w, err)
		return
	}
	release := rel.(*ct.Release)

	if err := schema.Validate(release); err != nil {
		respondWithError(w, err)
		return
	}

	app := c.getApp(ctx)
	c.appRepo.SetRelease(app, release.ID)
	httphelper.JSON(w, 200, release)
}
Esempio n. 13
0
File: app.go Progetto: imjorge/flynn
func (c *controllerAPI) UpdateApp(ctx context.Context, rw http.ResponseWriter, req *http.Request) {
	params, _ := ctxhelper.ParamsFromContext(ctx)

	var data appUpdate
	if err := httphelper.DecodeJSON(req, &data); err != nil {
		respondWithError(rw, err)
		return
	}

	if v, ok := data["meta"]; ok && v == nil {
		// handle {"meta": null}
		delete(data, "meta")
	}

	if err := schema.Validate(data); err != nil {
		respondWithError(rw, err)
		return
	}

	app, err := c.appRepo.Update(params.ByName("apps_id"), data)
	if err != nil {
		respondWithError(rw, err)
		return
	}
	httphelper.JSON(rw, 200, app)
}
Esempio n. 14
0
func (c *controllerAPI) UpdateAppMeta(ctx context.Context, rw http.ResponseWriter, req *http.Request) {
	params, _ := ctxhelper.ParamsFromContext(ctx)

	var data appUpdate
	if err := httphelper.DecodeJSON(req, &data); err != nil {
		respondWithError(rw, err)
		return
	}

	if err := schema.Validate(data); err != nil {
		respondWithError(rw, err)
		return
	}

	if data["meta"] == nil {
		data["meta"] = make(map[string]interface{})
	}

	app, err := c.appRepo.Update(params.ByName("apps_id"), data)
	if err != nil {
		respondWithError(rw, err)
		return
	}
	httphelper.JSON(rw, 200, app)
}
Esempio n. 15
0
File: http.go Progetto: devick/flynn
func (h *jobAPI) ConfigureDiscoverd(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	log := h.host.log.New("fn", "ConfigureDiscoverd")

	log.Info("decoding config")
	var config host.DiscoverdConfig
	if err := httphelper.DecodeJSON(r, &config); err != nil {
		log.Error("error decoding config", "err", err)
		httphelper.Error(w, err)
		return
	}
	log.Info("config decoded", "url", config.URL, "dns", config.DNS)

	h.host.statusMtx.Lock()
	h.host.status.Discoverd = &config
	h.host.statusMtx.Unlock()

	if config.URL != "" && config.DNS != "" {
		go h.host.discoverdOnce.Do(func() {
			log.Info("connecting to service discovery", "url", config.URL)
			if err := h.host.discMan.ConnectLocal(config.URL); err != nil {
				log.Error("error connecting to service discovery", "err", err)
				shutdown.Fatal(err)
			}
		})
	}
}
Esempio n. 16
0
func (h *jobAPI) UpdateTags(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	var tags map[string]string
	if err := httphelper.DecodeJSON(r, &tags); err != nil {
		httphelper.Error(w, err)
		return
	}
	if err := h.host.UpdateTags(tags); err != nil {
		httphelper.Error(w, err)
		return
	}
	w.WriteHeader(200)
}
Esempio n. 17
0
func (api *httpAPI) InstallHandler(w http.ResponseWriter, req *http.Request, params httprouter.Params) {
	var input *jsonInput
	if err := httphelper.DecodeJSON(req, &input); err != nil {
		httphelper.Error(w, err)
		return
	}
	api.InstallerStackMtx.Lock()
	defer api.InstallerStackMtx.Unlock()

	if len(api.InstallerStacks) > 0 {
		httphelper.ObjectExistsError(w, "install already started")
		return
	}

	var id = random.Hex(16)
	var creds aws.CredentialsProvider
	if input.Creds.AccessKeyID != "" && input.Creds.SecretAccessKey != "" {
		creds = aws.Creds(input.Creds.AccessKeyID, input.Creds.SecretAccessKey, "")
	} else {
		var err error
		creds, err = aws.EnvCreds()
		if err != nil {
			httphelper.ValidationError(w, "", err.Error())
			return
		}
	}
	s := &httpInstaller{
		ID:            id,
		PromptOutChan: make(chan *httpPrompt),
		PromptInChan:  make(chan *httpPrompt),
		logger:        log.New(),
		api:           api,
	}
	s.Stack = &Stack{
		Creds:        creds,
		Region:       input.Region,
		InstanceType: input.InstanceType,
		NumInstances: input.NumInstances,
		VpcCidr:      input.VpcCidr,
		SubnetCidr:   input.SubnetCidr,
		PromptInput:  s.PromptInput,
		YesNoPrompt:  s.YesNoPrompt,
	}
	if err := s.Stack.RunAWS(); err != nil {
		httphelper.Error(w, err)
		return
	}
	api.InstallerStacks[id] = s
	go s.handleEvents()
	httphelper.JSON(w, 200, s)
}
Esempio n. 18
0
func (c *controllerAPI) CreateRoute(ctx context.Context, w http.ResponseWriter, req *http.Request) {
	var route router.Route
	if err := httphelper.DecodeJSON(req, &route); err != nil {
		respondWithError(w, err)
		return
	}

	if err := createRoute(c.appRepo.db, c.routerc, c.getApp(ctx).ID, &route); err != nil {
		respondWithError(w, err)
		return
	}

	httphelper.JSON(w, 200, &route)
}
Esempio n. 19
0
func (h *jobAPI) ConfigureDiscoverd(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	log := h.host.log.New("fn", "ConfigureDiscoverd")

	log.Info("decoding config")
	config := &host.DiscoverdConfig{}
	if err := httphelper.DecodeJSON(r, config); err != nil {
		log.Error("error decoding config", "err", err)
		httphelper.Error(w, err)
		return
	}
	log.Info("config decoded", "url", config.URL, "dns", config.DNS)

	h.host.ConfigureDiscoverd(config)
}
Esempio n. 20
0
func (h *jobAPI) ConfigureNetworking(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	log := h.host.log.New("fn", "ConfigureNetworking")

	log.Info("decoding config")
	config := &host.NetworkConfig{}
	if err := httphelper.DecodeJSON(r, config); err != nil {
		log.Error("error decoding config", "err", err)
		shutdown.Fatal(err)
	}

	// configure the network before returning a response in case the
	// network coordinator requires the bridge to be created (e.g.
	// when using flannel with the "alloc" backend)
	h.host.ConfigureNetworking(config)
}
Esempio n. 21
0
func (h *jobAPI) ConfigureNetworking(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	config := &host.NetworkConfig{}
	if err := httphelper.DecodeJSON(r, config); err != nil {
		shutdown.Fatal(err)
	}

	go h.networkOnce.Do(func() {
		if err := h.host.backend.ConfigureNetworking(config); err != nil {
			shutdown.Fatal(err)
		}

		h.statusMtx.Lock()
		h.status.Network = config
		h.statusMtx.Unlock()
	})
}
Esempio n. 22
0
func (api *httpAPI) PromptHandler(w http.ResponseWriter, req *http.Request, params httprouter.Params) {
	api.InstallerPromptsMtx.Lock()
	prompt := api.InstallerPrompts[params.ByName("id")]
	api.InstallerPromptsMtx.Unlock()
	if prompt == nil {
		httphelper.ObjectNotFoundError(w, "prompt not found")
		return
	}

	var input *httpPrompt
	if err := httphelper.DecodeJSON(req, &input); err != nil {
		httphelper.Error(w, err)
		return
	}
	prompt.Resolve(input)
	w.WriteHeader(200)
}
Esempio n. 23
0
func (c *controllerAPI) UpdateRoute(ctx context.Context, w http.ResponseWriter, req *http.Request) {
	var route *router.Route
	if err := httphelper.DecodeJSON(req, &route); err != nil {
		respondWithError(w, err)
		return
	}

	err := c.routerc.UpdateRoute(route)
	if err == routerc.ErrNotFound {
		err = ErrNotFound
	}
	if err != nil {
		respondWithError(w, err)
		return
	}
	httphelper.JSON(w, 200, route)
}
Esempio n. 24
0
func (h *httpAPI) SetServiceMeta(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
	meta := &discoverd.ServiceMeta{}
	if err := hh.DecodeJSON(r, meta); err != nil {
		hh.Error(w, err)
		return
	}

	if err := h.Store.SetServiceMeta(params.ByName("service"), meta); err != nil {
		if IsNotFound(err) {
			hh.ObjectNotFoundError(w, err.Error())
		} else {
			hh.Error(w, err)
		}
		return
	}

	hh.JSON(w, 200, meta)
}
Esempio n. 25
0
func (c *controllerAPI) ProvisionResource(ctx context.Context, w http.ResponseWriter, req *http.Request) {
	p, err := c.getProvider(ctx)
	if err != nil {
		respondWithError(w, err)
		return
	}

	var rr ct.ResourceReq
	if err = httphelper.DecodeJSON(req, &rr); err != nil {
		respondWithError(w, err)
		return
	}

	var config []byte
	if rr.Config != nil {
		config = *rr.Config
	} else {
		config = []byte(`{}`)
	}
	data, err := resource.Provision(p.URL, config)
	if err != nil {
		respondWithError(w, err)
		return
	}

	res := &ct.Resource{
		ProviderID: p.ID,
		ExternalID: data.ID,
		Env:        data.Env,
		Apps:       rr.Apps,
	}

	if err := schema.Validate(res); err != nil {
		respondWithError(w, err)
		return
	}

	if err := c.resourceRepo.Add(res); err != nil {
		// TODO: attempt to "rollback" provisioning
		respondWithError(w, err)
		return
	}
	httphelper.JSON(w, 200, res)
}
Esempio n. 26
0
func (h *httpAPI) SetLeader(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
	service := params.ByName("service")
	config := h.Store.GetConfig(service)
	if config == nil || config.LeaderType != discoverd.LeaderTypeManual {
		hh.ValidationError(w, "", "service leader election type is not manual")
		return
	}

	inst := &discoverd.Instance{}
	if err := hh.DecodeJSON(r, inst); err != nil {
		hh.Error(w, err)
		return
	}

	if err := h.Store.SetLeader(service, inst.ID); err != nil {
		hh.Error(w, err)
		return
	}
}
Esempio n. 27
0
func (h *jobAPI) ConfigureDiscoverd(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	var config struct {
		URL string `json:"url"`
	}
	if err := httphelper.DecodeJSON(r, &config); err != nil {
		httphelper.Error(w, err)
		return
	}

	go h.discoverdOnce.Do(func() {
		if err := h.connectDiscoverd(config.URL); err != nil {
			shutdown.Fatal(err)
		}

		h.statusMtx.Lock()
		h.status.Discoverd = &host.DiscoverdConfig{URL: config.URL}
		h.statusMtx.Unlock()
	})
}
Esempio n. 28
0
func (h *jobAPI) ConfigureNetworking(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	config := &host.NetworkConfig{}
	if err := httphelper.DecodeJSON(r, config); err != nil {
		shutdown.Fatal(err)
	}

	// configure the network before returning a response in case the
	// network coordinator requires the bridge to be created (e.g.
	// when using flannel with the "alloc" backend)
	h.networkOnce.Do(func() {
		if err := h.host.backend.ConfigureNetworking(config); err != nil {
			shutdown.Fatal(err)
		}

		h.host.statusMtx.Lock()
		h.host.status.Network = config
		h.host.statusMtx.Unlock()
	})
}
Esempio n. 29
0
File: http.go Progetto: devick/flynn
func (h *jobAPI) AddJob(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
	// TODO(titanous): validate UUID
	id := ps.ByName("id")

	log := h.host.log.New("fn", "AddJob", "job.id", id)

	if shutdown.IsActive() {
		log.Warn("refusing to start job due to active shutdown")
		httphelper.JSON(w, 500, struct{}{})
		return
	}

	log.Info("decoding job")
	job := &host.Job{ID: id}
	if err := httphelper.DecodeJSON(r, job); err != nil {
		log.Error("error decoding job", "err", err)
		httphelper.Error(w, err)
		return
	}

	log.Info("acquiring state database")
	if err := h.host.state.Acquire(); err != nil {
		log.Error("error acquiring state database", "err", err)
		httphelper.Error(w, err)
		return
	}

	h.host.state.AddJob(job)

	go func() {
		// TODO(titanous): ratelimit this goroutine?
		log.Info("running job")
		err := h.host.backend.Run(job, nil)
		h.host.state.Release()
		if err != nil {
			log.Error("error running job", "err", err)
			h.host.state.SetStatusFailed(job.ID, err)
		}
	}()

	// TODO(titanous): return 201 Accepted
	httphelper.JSON(w, 200, struct{}{})
}
Esempio n. 30
0
func (h *jobAPI) ConfigureDiscoverd(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	var config host.DiscoverdConfig
	if err := httphelper.DecodeJSON(r, &config); err != nil {
		httphelper.Error(w, err)
		return
	}

	h.host.statusMtx.Lock()
	h.host.status.Discoverd = &config
	h.host.statusMtx.Unlock()

	if config.URL != "" && config.DNS != "" {
		go h.discoverdOnce.Do(func() {
			if err := h.connectDiscoverd(config.URL); err != nil {
				shutdown.Fatal(err)
			}
		})
	}
}