| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195 |
- package provision
- import (
- "encoding/json"
- "fmt"
- "net/http"
- "time"
- "github.com/porter-dev/porter/api/server/shared"
- "github.com/porter-dev/porter/api/server/shared/apierrors"
- "github.com/porter-dev/porter/api/types"
- "github.com/porter-dev/porter/internal/analytics"
- "github.com/porter-dev/porter/internal/models"
- "github.com/porter-dev/porter/internal/random"
- "github.com/porter-dev/porter/provisioner/integrations/provisioner"
- "github.com/porter-dev/porter/provisioner/integrations/redis_stream"
- "github.com/porter-dev/porter/provisioner/server/config"
- "golang.org/x/crypto/bcrypt"
- ptypes "github.com/porter-dev/porter/provisioner/types"
- )
- type ProvisionApplyHandler struct {
- Config *config.Config
- decoderValidator shared.RequestDecoderValidator
- resultWriter shared.ResultWriter
- }
- func NewProvisionApplyHandler(
- config *config.Config,
- ) *ProvisionApplyHandler {
- return &ProvisionApplyHandler{
- Config: config,
- decoderValidator: shared.NewDefaultRequestDecoderValidator(config.Logger, config.Alerter),
- resultWriter: shared.NewDefaultResultWriter(config.Logger, config.Alerter),
- }
- }
- func (c *ProvisionApplyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- // read the project and infra from the attached scope
- infra, _ := r.Context().Value(types.InfraScope).(*models.Infra)
- req := &ptypes.ApplyBaseRequest{}
- if ok := c.decoderValidator.DecodeAndValidate(w, r, req); !ok {
- return
- }
- // create a new operation and write it to the database
- operationUID, err := models.GetOperationID()
- if err != nil {
- apierrors.HandleAPIError(c.Config.Logger, c.Config.Alerter, w, r, apierrors.NewErrInternal(err), true)
- return
- }
- // parse values to JSON to store in the operation
- valuesJSON, err := json.Marshal(req.Values)
- if err != nil {
- apierrors.HandleAPIError(c.Config.Logger, c.Config.Alerter, w, r, apierrors.NewErrInternal(err), true)
- return
- }
- operation := &models.Operation{
- UID: operationUID,
- InfraID: infra.ID,
- Type: req.OperationKind,
- Status: "starting",
- LastApplied: valuesJSON,
- TemplateVersion: "v0.1.0",
- }
- operation, err = c.Config.Repo.Infra().AddOperation(infra, operation)
- if err != nil {
- apierrors.HandleAPIError(c.Config.Logger, c.Config.Alerter, w, r, apierrors.NewErrInternal(err), true)
- return
- }
- ceToken, rawToken, err := createCredentialsExchangeToken(c.Config, infra)
- if err != nil {
- apierrors.HandleAPIError(c.Config.Logger, c.Config.Alerter, w, r, apierrors.NewErrInternal(err), true)
- return
- }
- // push a first message to the operation stream
- err = redis_stream.PushToOperationStream(c.Config.RedisClient, infra, operation, &ptypes.TFResourceState{
- Status: "OPERATION_STARTED",
- })
- if err != nil {
- apierrors.HandleAPIError(c.Config.Logger, c.Config.Alerter, w, r, apierrors.NewErrInternal(err), true)
- return
- }
- // spawn a new provisioning process
- err = c.Config.Provisioner.Provision(&provisioner.ProvisionOpts{
- Infra: infra,
- Operation: operation,
- OperationKind: provisioner.Apply,
- Kind: req.Kind,
- Values: req.Values,
- CredentialExchange: &provisioner.ProvisionCredentialExchange{
- CredExchangeEndpoint: fmt.Sprintf(
- "%s/api/v1/%s/credentials",
- c.Config.ProvisionerConf.ProvisionerCredExchangeURL,
- models.GetWorkspaceID(infra, operation),
- ),
- CredExchangeToken: rawToken,
- CredExchangeID: ceToken.ID,
- },
- })
- if err != nil {
- apierrors.HandleAPIError(c.Config.Logger, c.Config.Alerter, w, r, apierrors.NewErrInternal(err), true)
- return
- }
- // update the infrastructure as either "updating" or "creating"
- if req.OperationKind == "create" || req.OperationKind == "retry_create" {
- infra.Status = types.InfraStatus("creating")
- } else if req.OperationKind == "update" {
- infra.Status = types.InfraStatus("updating")
- }
- infra, err = c.Config.Repo.Infra().UpdateInfra(infra)
- if err != nil {
- apierrors.HandleAPIError(c.Config.Logger, c.Config.Alerter, w, r, apierrors.NewErrInternal(err), true)
- return
- }
- op, err := operation.ToOperationType()
- if err != nil {
- apierrors.HandleAPIError(c.Config.Logger, c.Config.Alerter, w, r, apierrors.NewErrInternal(err), true)
- return
- }
- // return the operation response type to the server
- c.resultWriter.WriteResult(w, r, op)
- // if this is a cluster or registry infra type, send to analytics client
- switch infra.Kind {
- case types.InfraDOKS, types.InfraEKS, types.InfraGKE, types.InfraAKS:
- c.Config.AnalyticsClient.Track(analytics.ClusterProvisioningStartTrack(
- &analytics.ClusterProvisioningStartTrackOpts{
- ProjectScopedTrackOpts: analytics.GetProjectScopedTrackOpts(0, infra.ProjectID),
- ClusterType: infra.Kind,
- InfraID: infra.ID,
- },
- ))
- case types.InfraDOCR, types.InfraECR, types.InfraGCR, types.InfraGAR, types.InfraACR:
- c.Config.AnalyticsClient.Track(analytics.RegistryProvisioningStartTrack(
- &analytics.RegistryProvisioningStartTrackOpts{
- ProjectScopedTrackOpts: analytics.GetProjectScopedTrackOpts(0, infra.ProjectID),
- RegistryType: infra.Kind,
- InfraID: infra.ID,
- },
- ))
- }
- }
- func createCredentialsExchangeToken(conf *config.Config, infra *models.Infra) (*models.CredentialsExchangeToken, string, error) {
- // convert the form to a project model
- expiry := time.Now().Add(6 * time.Hour)
- rawToken, err := random.StringWithCharset(32, "")
- if err != nil {
- return nil, "", err
- }
- hashedToken, err := bcrypt.GenerateFromPassword([]byte(rawToken), 8)
- if err != nil {
- return nil, "", err
- }
- ceToken := &models.CredentialsExchangeToken{
- ProjectID: infra.ProjectID,
- Expiry: &expiry,
- Token: hashedToken,
- DOCredentialID: infra.DOIntegrationID,
- AWSCredentialID: infra.AWSIntegrationID,
- GCPCredentialID: infra.GCPIntegrationID,
- AzureCredentialID: infra.AzureIntegrationID,
- }
- // handle write to the database
- ceToken, err = conf.Repo.CredentialsExchangeToken().CreateCredentialsExchangeToken(ceToken)
- if err != nil {
- return nil, "", err
- }
- return ceToken, rawToken, nil
- }
|