update.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. package environment_groups
  2. import (
  3. "encoding/base64"
  4. "net/http"
  5. "time"
  6. "connectrpc.com/connect"
  7. porterv1 "github.com/porter-dev/api-contracts/generated/go/porter/v1"
  8. "github.com/porter-dev/porter/api/server/authz"
  9. "github.com/porter-dev/porter/api/server/handlers"
  10. "github.com/porter-dev/porter/api/server/shared"
  11. "github.com/porter-dev/porter/api/server/shared/apierrors"
  12. "github.com/porter-dev/porter/api/server/shared/config"
  13. "github.com/porter-dev/porter/api/types"
  14. "github.com/porter-dev/porter/internal/models"
  15. "github.com/porter-dev/porter/internal/telemetry"
  16. )
  17. type UpdateEnvironmentGroupHandler struct {
  18. handlers.PorterHandlerReadWriter
  19. authz.KubernetesAgentGetter
  20. }
  21. func NewUpdateEnvironmentGroupHandler(
  22. config *config.Config,
  23. decoderValidator shared.RequestDecoderValidator,
  24. writer shared.ResultWriter,
  25. ) *UpdateEnvironmentGroupHandler {
  26. return &UpdateEnvironmentGroupHandler{
  27. PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
  28. KubernetesAgentGetter: authz.NewOutOfClusterAgentGetter(config),
  29. }
  30. }
  31. // EnvironmentGroupType is the env_groups-level environment group type
  32. type EnvironmentGroupType string
  33. const (
  34. // EnvironmentGroupType_Unspecified is the nil environment group type
  35. EnvironmentGroupType_Unspecified EnvironmentGroupType = ""
  36. // EnvironmentGroupType_Doppler is the doppler environment group type
  37. EnvironmentGroupType_Doppler EnvironmentGroupType = "doppler"
  38. // EnvironmentGroupType_Porter is the porter environment group type
  39. EnvironmentGroupType_Porter EnvironmentGroupType = "porter"
  40. // EnvironmentGroupType_Datastore is the datastore environment group type
  41. EnvironmentGroupType_Datastore EnvironmentGroupType = "datastore"
  42. // EnvironmentGroupType_Infisical is the infisical environment group type
  43. EnvironmentGroupType_Infisical EnvironmentGroupType = "infisical"
  44. )
  45. // EnvVariableDeletions is the set of keys to delete from the environment group
  46. type EnvVariableDeletions struct {
  47. // Variables is a set of variable keys to delete from the environment group
  48. Variables []string `json:"variables"`
  49. // Secrets is a set of secret variable keys to delete from the environment group
  50. Secrets []string `json:"secrets"`
  51. }
  52. // InfisicalEnv is the Infisical environment to pull secret values from, only required for the Infisical external provider type
  53. type InfisicalEnv struct {
  54. // Slug is the slug referring to the Infisical environment to pull secret values from
  55. Slug string `json:"slug"`
  56. // Path is the relative path in the Infisical environment to pull secret values from
  57. Path string `json:"path"`
  58. }
  59. type UpdateEnvironmentGroupRequest struct {
  60. // Name of the env group to create or update
  61. Name string `json:"name"`
  62. // Type of the env group to create or update
  63. Type EnvironmentGroupType `json:"type"`
  64. // AuthToken for the env group
  65. AuthToken string `json:"auth_token"`
  66. // Variables are values which are not sensitive. All values must be a string due to a kubernetes limitation.
  67. Variables map[string]string `json:"variables"`
  68. // SecretVariables are sensitive values. All values must be a string due to a kubernetes limitation.
  69. SecretVariables map[string]string `json:"secret_variables"`
  70. // Files is a list of files associated with the env group
  71. Files []EnvironmentGroupFile `json:"files"`
  72. // IsEnvOverride is a flag to determine if provided variables should override or merge with existing variables
  73. IsEnvOverride bool `json:"is_env_override"`
  74. // Deletions is a set of keys to delete from the environment group
  75. Deletions EnvVariableDeletions `json:"deletions"`
  76. // SkipAppAutoDeploy is a flag to determine if the app should be auto deployed
  77. SkipAppAutoDeploy bool `json:"skip_app_auto_deploy"`
  78. // InfisicalEnv is the Infisical environment to pull secret values from, only required for the Infisical external provider type
  79. InfisicalEnv InfisicalEnv `json:"infisical_env"`
  80. }
  81. type UpdateEnvironmentGroupResponse struct {
  82. // Name of the env group to create or update
  83. Name string `json:"name"`
  84. // Variables are variables which should are not sensitive. All values must be a string due to a kubernetes limitation.
  85. Variables map[string]string `json:"variables,omitempty"`
  86. // SecretVariables are sensitive variables. All values must be a string due to a kubernetes limitation.
  87. SecretVariables map[string]string `json:"secret_variables,omitempty"`
  88. CreatedAt time.Time `json:"created_at"`
  89. }
  90. func (c *UpdateEnvironmentGroupHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  91. ctx, span := telemetry.NewSpan(r.Context(), "serve-update-env-group")
  92. defer span.End()
  93. request := &UpdateEnvironmentGroupRequest{}
  94. if ok := c.DecodeAndValidate(w, r, request); !ok {
  95. return
  96. }
  97. cluster, _ := ctx.Value(types.ClusterScope).(*models.Cluster)
  98. telemetry.WithAttributes(span,
  99. telemetry.AttributeKV{Key: "environment-group-name", Value: request.Name},
  100. telemetry.AttributeKV{Key: "environment-group-type", Value: request.Type},
  101. )
  102. switch request.Type {
  103. case EnvironmentGroupType_Doppler, EnvironmentGroupType_Infisical:
  104. var provider porterv1.EnumEnvGroupProviderType
  105. var infisicalEnv *porterv1.InfisicalEnv
  106. if request.Type == EnvironmentGroupType_Doppler {
  107. provider = porterv1.EnumEnvGroupProviderType_ENUM_ENV_GROUP_PROVIDER_TYPE_DOPPLER
  108. }
  109. if request.Type == EnvironmentGroupType_Infisical {
  110. if request.InfisicalEnv.Slug == "" {
  111. err := telemetry.Error(ctx, span, nil, "infisical env slug is required")
  112. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
  113. return
  114. }
  115. if request.InfisicalEnv.Path == "" {
  116. err := telemetry.Error(ctx, span, nil, "infisical env path is required")
  117. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
  118. return
  119. }
  120. provider = porterv1.EnumEnvGroupProviderType_ENUM_ENV_GROUP_PROVIDER_TYPE_INFISICAL
  121. infisicalEnv = &porterv1.InfisicalEnv{
  122. EnvironmentSlug: request.InfisicalEnv.Slug,
  123. EnvironmentPath: request.InfisicalEnv.Path,
  124. }
  125. }
  126. _, err := c.Config().ClusterControlPlaneClient.CreateOrUpdateEnvGroup(ctx, connect.NewRequest(&porterv1.CreateOrUpdateEnvGroupRequest{
  127. ProjectId: int64(cluster.ProjectID),
  128. ClusterId: int64(cluster.ID),
  129. EnvGroupProviderType: provider,
  130. EnvGroupName: request.Name,
  131. EnvGroupAuthToken: request.AuthToken,
  132. InfisicalEnv: infisicalEnv,
  133. }))
  134. if err != nil {
  135. err := telemetry.Error(ctx, span, err, "unable to create environment group")
  136. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  137. return
  138. }
  139. default:
  140. var files []*porterv1.EnvGroupFile
  141. for _, file := range request.Files {
  142. contents := file.Contents
  143. encoded := base64.StdEncoding.EncodeToString([]byte(contents))
  144. files = append(files, &porterv1.EnvGroupFile{
  145. Name: file.Name,
  146. B64Contents: encoded,
  147. })
  148. }
  149. _, err := c.Config().ClusterControlPlaneClient.CreateOrUpdateEnvGroup(ctx, connect.NewRequest(&porterv1.CreateOrUpdateEnvGroupRequest{
  150. ProjectId: int64(cluster.ProjectID),
  151. ClusterId: int64(cluster.ID),
  152. EnvGroupProviderType: porterv1.EnumEnvGroupProviderType_ENUM_ENV_GROUP_PROVIDER_TYPE_PORTER,
  153. EnvGroupName: request.Name,
  154. EnvVars: &porterv1.EnvGroupVariables{
  155. Normal: request.Variables,
  156. Secret: request.SecretVariables,
  157. Files: files,
  158. },
  159. EnvVariableDeletions: &porterv1.EnvVariableDeletions{
  160. Variables: request.Deletions.Variables,
  161. Secrets: request.Deletions.Secrets,
  162. },
  163. IsEnvOverride: request.IsEnvOverride,
  164. SkipAppAutoDeploy: request.SkipAppAutoDeploy,
  165. }))
  166. if err != nil {
  167. err := telemetry.Error(ctx, span, err, "unable to create environment group")
  168. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  169. return
  170. }
  171. }
  172. envGroupResponse := &UpdateEnvironmentGroupResponse{
  173. Name: request.Name,
  174. CreatedAt: time.Now().UTC(),
  175. }
  176. c.WriteResult(w, r, envGroupResponse)
  177. }