create.go 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684
  1. package release
  2. import (
  3. "context"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "net/http"
  8. "strings"
  9. "time"
  10. "github.com/porter-dev/porter/internal/telemetry"
  11. "github.com/porter-dev/porter/api/server/authz"
  12. "github.com/porter-dev/porter/api/server/handlers"
  13. "github.com/porter-dev/porter/api/server/shared"
  14. "github.com/porter-dev/porter/api/server/shared/apierrors"
  15. "github.com/porter-dev/porter/api/server/shared/config"
  16. "github.com/porter-dev/porter/api/types"
  17. "github.com/porter-dev/porter/internal/analytics"
  18. "github.com/porter-dev/porter/internal/auth/token"
  19. "github.com/porter-dev/porter/internal/encryption"
  20. "github.com/porter-dev/porter/internal/helm"
  21. "github.com/porter-dev/porter/internal/helm/loader"
  22. "github.com/porter-dev/porter/internal/helm/repo"
  23. "github.com/porter-dev/porter/internal/integrations/ci/actions"
  24. "github.com/porter-dev/porter/internal/integrations/ci/gitlab"
  25. "github.com/porter-dev/porter/internal/models"
  26. "github.com/porter-dev/porter/internal/oauth"
  27. "github.com/porter-dev/porter/internal/registry"
  28. "github.com/stefanmcshane/helm/pkg/release"
  29. "golang.org/x/crypto/bcrypt"
  30. "gopkg.in/yaml.v2"
  31. v1 "k8s.io/api/core/v1"
  32. )
  33. type CreateReleaseHandler struct {
  34. handlers.PorterHandlerReadWriter
  35. authz.KubernetesAgentGetter
  36. }
  37. func NewCreateReleaseHandler(
  38. config *config.Config,
  39. decoderValidator shared.RequestDecoderValidator,
  40. writer shared.ResultWriter,
  41. ) *CreateReleaseHandler {
  42. return &CreateReleaseHandler{
  43. PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
  44. KubernetesAgentGetter: authz.NewOutOfClusterAgentGetter(config),
  45. }
  46. }
  47. func (c *CreateReleaseHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  48. tracer, _ := telemetry.InitTracer(r.Context(), c.Config().TelemetryConfig)
  49. defer tracer.Shutdown()
  50. user, _ := r.Context().Value(types.UserScope).(*models.User)
  51. cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
  52. namespace := r.Context().Value(types.NamespaceScope).(string)
  53. operationID := oauth.CreateRandomState()
  54. ctx, span := telemetry.NewSpan(r.Context(), "serve-create-release")
  55. defer span.End()
  56. telemetry.WithAttributes(span,
  57. telemetry.AttributeKV{Key: "project-id", Value: cluster.ProjectID},
  58. telemetry.AttributeKV{Key: "cluster-id", Value: cluster.ID},
  59. telemetry.AttributeKV{Key: "user-email", Value: user.Email},
  60. telemetry.AttributeKV{Key: "namespace", Value: namespace},
  61. )
  62. c.Config().AnalyticsClient.Track(analytics.ApplicationLaunchStartTrack(
  63. &analytics.ApplicationLaunchStartTrackOpts{
  64. ClusterScopedTrackOpts: analytics.GetClusterScopedTrackOpts(user.ID, cluster.ProjectID, cluster.ID),
  65. FlowID: operationID,
  66. },
  67. ))
  68. helmAgent, err := c.GetHelmAgent(ctx, r, cluster, "")
  69. if err != nil {
  70. c.HandleAPIError(w, r, apierrors.NewErrInternal(telemetry.Error(ctx, span, err, "error getting helm agent")))
  71. return
  72. }
  73. request := &types.CreateReleaseRequest{}
  74. if ok := c.DecodeAndValidate(w, r, request); !ok {
  75. return
  76. }
  77. if request.RepoURL == "" {
  78. request.RepoURL = c.Config().ServerConf.DefaultApplicationHelmRepoURL
  79. }
  80. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "repo-url", Value: request.RepoURL})
  81. // if the repo url is not an addon or application url, validate against the helm repos
  82. if request.RepoURL != c.Config().ServerConf.DefaultAddonHelmRepoURL && request.RepoURL != c.Config().ServerConf.DefaultApplicationHelmRepoURL {
  83. // load the helm repos in the project
  84. hrs, err := c.Repo().HelmRepo().ListHelmReposByProjectID(cluster.ProjectID)
  85. if err != nil {
  86. c.HandleAPIError(w, r, apierrors.NewErrInternal(telemetry.Error(ctx, span, err, "error listing helm repos for project")))
  87. return
  88. }
  89. isValid := repo.ValidateRepoURL(c.Config().ServerConf.DefaultAddonHelmRepoURL, c.Config().ServerConf.DefaultApplicationHelmRepoURL, hrs, request.RepoURL)
  90. if !isValid {
  91. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
  92. telemetry.Error(ctx, span, err, "invalid repo_url parameter"),
  93. http.StatusBadRequest,
  94. ))
  95. return
  96. }
  97. }
  98. if request.TemplateVersion == "latest" {
  99. request.TemplateVersion = ""
  100. }
  101. chart, err := loader.LoadChartPublic(ctx, request.RepoURL, request.TemplateName, request.TemplateVersion)
  102. if err != nil {
  103. c.HandleAPIError(w, r, apierrors.NewErrInternal(telemetry.Error(ctx, span, err, "error loading public chart")))
  104. return
  105. }
  106. registries, err := c.Repo().Registry().ListRegistriesByProjectID(cluster.ProjectID)
  107. if err != nil {
  108. c.HandleAPIError(w, r, apierrors.NewErrInternal(telemetry.Error(ctx, span, err, "error listing registries")))
  109. return
  110. }
  111. k8sAgent, err := c.GetAgent(r, cluster, "")
  112. if err != nil {
  113. c.HandleAPIError(w, r, apierrors.NewErrInternal(telemetry.Error(ctx, span, err, "error getting k8s agent")))
  114. return
  115. }
  116. // create the namespace if it does not exist already
  117. _, err = k8sAgent.CreateNamespace(namespace, nil)
  118. if err != nil {
  119. c.HandleAPIError(w, r, apierrors.NewErrInternal(telemetry.Error(ctx, span, err, "error creating namespace")))
  120. return
  121. }
  122. conf := &helm.InstallChartConfig{
  123. Chart: chart,
  124. Name: request.Name,
  125. Namespace: namespace,
  126. Values: request.Values,
  127. Cluster: cluster,
  128. Repo: c.Repo(),
  129. Registries: registries,
  130. }
  131. helmRelease, err := helmAgent.InstallChart(ctx, conf, c.Config().DOConf, c.Config().ServerConf.DisablePullSecretsInjection)
  132. if err != nil {
  133. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
  134. telemetry.Error(ctx, span, err, "error installing a new chart"),
  135. http.StatusBadRequest,
  136. ))
  137. return
  138. }
  139. configMaps := make([]*v1.ConfigMap, 0)
  140. if request.SyncedEnvGroups != nil && len(request.SyncedEnvGroups) > 0 {
  141. for _, envGroupName := range request.SyncedEnvGroups {
  142. // read the attached configmap
  143. cm, _, err := k8sAgent.GetLatestVersionedConfigMap(envGroupName, namespace)
  144. if err != nil {
  145. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(telemetry.Error(ctx, span, err, "Couldn't find the env group"), http.StatusNotFound))
  146. return
  147. }
  148. configMaps = append(configMaps, cm)
  149. }
  150. }
  151. release, err := CreateAppReleaseFromHelmRelease(ctx, c.Config(), cluster.ProjectID, cluster.ID, 0, helmRelease)
  152. if err != nil {
  153. c.HandleAPIError(w, r, apierrors.NewErrInternal(telemetry.Error(ctx, span, err, "error creating app release from helm release")))
  154. return
  155. }
  156. if len(configMaps) > 0 {
  157. for _, cm := range configMaps {
  158. _, err = k8sAgent.AddApplicationToVersionedConfigMap(cm, release.Name)
  159. if err != nil {
  160. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "release-name", Value: release.Name})
  161. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "config-map-name", Value: cm.Name})
  162. c.HandleAPIErrorNoWrite(w, r, apierrors.NewErrInternal(telemetry.Error(ctx, span, err, "Couldn't add release to the config map")))
  163. }
  164. }
  165. }
  166. if request.Tags != nil {
  167. tags, err := c.Repo().Tag().LinkTagsToRelease(request.Tags, release)
  168. if err == nil {
  169. release.Tags = append(release.Tags, tags...)
  170. }
  171. }
  172. if request.BuildConfig != nil {
  173. _, err = createBuildConfig(ctx, c.Config(), release, request.BuildConfig)
  174. }
  175. if err != nil {
  176. c.HandleAPIError(w, r, apierrors.NewErrInternal(telemetry.Error(ctx, span, err, "error building config")))
  177. return
  178. }
  179. if request.GitActionConfig != nil {
  180. _, _, err := createGitAction(
  181. ctx,
  182. c.Config(),
  183. user.ID,
  184. cluster.ProjectID,
  185. cluster.ID,
  186. request.GitActionConfig,
  187. request.Name,
  188. namespace,
  189. release,
  190. )
  191. if err != nil {
  192. unwrappedErr := errors.Unwrap(err)
  193. if unwrappedErr != nil {
  194. if errors.Is(unwrappedErr, actions.ErrProtectedBranch) {
  195. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(telemetry.Error(ctx, span, err, "error creating git action on protected branch"), http.StatusConflict))
  196. } else if errors.Is(unwrappedErr, actions.ErrCreatePRForProtectedBranch) {
  197. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(telemetry.Error(ctx, span, err, "error creating PR on protected branch"), http.StatusPreconditionFailed))
  198. }
  199. } else {
  200. c.HandleAPIError(w, r, apierrors.NewErrInternal(telemetry.Error(ctx, span, err, "error creating git action")))
  201. return
  202. }
  203. }
  204. }
  205. c.Config().AnalyticsClient.Track(analytics.ApplicationLaunchSuccessTrack(
  206. &analytics.ApplicationLaunchSuccessTrackOpts{
  207. ApplicationScopedTrackOpts: analytics.GetApplicationScopedTrackOpts(
  208. user.ID,
  209. cluster.ProjectID,
  210. cluster.ID,
  211. release.Name,
  212. release.Namespace,
  213. chart.Metadata.Name,
  214. ),
  215. FlowID: operationID,
  216. },
  217. ))
  218. w.WriteHeader(http.StatusCreated)
  219. }
  220. func CreateAppReleaseFromHelmRelease(
  221. ctx context.Context,
  222. config *config.Config,
  223. projectID, clusterID, stackResourceID uint,
  224. helmRelease *release.Release,
  225. ) (*models.Release, error) {
  226. ctx, span := telemetry.NewSpan(ctx, "create-app-release-from-helm-release")
  227. defer span.End()
  228. telemetry.WithAttributes(span,
  229. telemetry.AttributeKV{Key: "project-id", Value: projectID},
  230. telemetry.AttributeKV{Key: "cluster-id", Value: clusterID},
  231. telemetry.AttributeKV{Key: "stack-resource-id", Value: stackResourceID},
  232. telemetry.AttributeKV{Key: "helm-release-name", Value: helmRelease.Name},
  233. )
  234. token, err := encryption.GenerateRandomBytes(16)
  235. if err != nil {
  236. return nil, err
  237. }
  238. // create release with webhook token in db
  239. image, ok := helmRelease.Config["image"].(map[string]interface{})
  240. if !ok {
  241. return nil, telemetry.Error(ctx, span, nil, "Could not find field image in config")
  242. }
  243. repository := image["repository"]
  244. repoStr, ok := repository.(string)
  245. if !ok {
  246. return nil, telemetry.Error(ctx, span, nil, "Could not find field repository in config")
  247. }
  248. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "repo-uri", Value: repoStr})
  249. release := &models.Release{
  250. ClusterID: clusterID,
  251. ProjectID: projectID,
  252. Namespace: helmRelease.Namespace,
  253. Name: helmRelease.Name,
  254. WebhookToken: token,
  255. ImageRepoURI: repoStr,
  256. StackResourceID: stackResourceID,
  257. }
  258. return config.Repo.Release().CreateRelease(release)
  259. }
  260. func CreateAddonReleaseFromHelmRelease(
  261. config *config.Config,
  262. projectID, clusterID, stackResourceID uint,
  263. helmRelease *release.Release,
  264. ) (*models.Release, error) {
  265. release := &models.Release{
  266. ClusterID: clusterID,
  267. ProjectID: projectID,
  268. Namespace: helmRelease.Namespace,
  269. Name: helmRelease.Name,
  270. StackResourceID: stackResourceID,
  271. }
  272. return config.Repo.Release().CreateRelease(release)
  273. }
  274. func createGitAction(
  275. ctx context.Context,
  276. config *config.Config,
  277. userID, projectID, clusterID uint,
  278. request *types.CreateGitActionConfigRequest,
  279. name, namespace string,
  280. release *models.Release,
  281. ) (*types.GitActionConfig, []byte, error) {
  282. ctx, span := telemetry.NewSpan(ctx, "create-git-action")
  283. defer span.End()
  284. telemetry.WithAttributes(span,
  285. telemetry.AttributeKV{Key: "project-id", Value: projectID},
  286. telemetry.AttributeKV{Key: "cluster-id", Value: clusterID},
  287. telemetry.AttributeKV{Key: "user-id", Value: userID},
  288. telemetry.AttributeKV{Key: "name", Value: name},
  289. telemetry.AttributeKV{Key: "namespace", Value: namespace},
  290. )
  291. // if the registry was provisioned through Porter, create a repository if necessary
  292. if release != nil && request.RegistryID != 0 {
  293. // read the registry
  294. reg, err := config.Repo.Registry().ReadRegistry(projectID, request.RegistryID)
  295. if err != nil {
  296. return nil, nil, telemetry.Error(ctx, span, err, "could not read repo registry")
  297. }
  298. _reg := registry.Registry(*reg)
  299. regAPI := &_reg
  300. // parse the name from the registry
  301. nameSpl := strings.Split(request.ImageRepoURI, "/")
  302. repoName := nameSpl[len(nameSpl)-1]
  303. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "repo-name", Value: repoName})
  304. err = regAPI.CreateRepository(ctx, config, repoName)
  305. if err != nil {
  306. return nil, nil, telemetry.Error(ctx, span, err, "could not create repo")
  307. }
  308. }
  309. isDryRun := release == nil
  310. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "is-dry-run", Value: isDryRun})
  311. repoSplit := strings.Split(request.GitRepo, "/")
  312. if len(repoSplit) != 2 {
  313. return nil, nil, fmt.Errorf("invalid formatting of repo name")
  314. }
  315. encoded := ""
  316. var err error
  317. // if this isn't a dry run, generate the token
  318. if !isDryRun {
  319. encoded, err = getToken(ctx, config, userID, projectID, clusterID, request)
  320. if err != nil {
  321. return nil, nil, telemetry.Error(ctx, span, err, "error getting token")
  322. }
  323. }
  324. var workflowYAML []byte
  325. var gitErr error
  326. if request.GitlabIntegrationID != 0 {
  327. giRunner := &gitlab.GitlabCI{
  328. ServerURL: config.ServerConf.ServerURL,
  329. GitRepoOwner: repoSplit[0],
  330. GitRepoName: repoSplit[1],
  331. GitBranch: request.GitBranch,
  332. Repo: config.Repo,
  333. ProjectID: projectID,
  334. ClusterID: clusterID,
  335. UserID: userID,
  336. IntegrationID: request.GitlabIntegrationID,
  337. PorterConf: config,
  338. ReleaseName: name,
  339. ReleaseNamespace: namespace,
  340. FolderPath: request.FolderPath,
  341. PorterToken: encoded,
  342. }
  343. gitErr = giRunner.Setup()
  344. } else {
  345. // create the commit in the git repo
  346. gaRunner := &actions.GithubActions{
  347. InstanceName: config.ServerConf.InstanceName,
  348. ServerURL: config.ServerConf.ServerURL,
  349. GithubOAuthIntegration: nil,
  350. GithubAppID: config.GithubAppConf.AppID,
  351. GithubAppSecretPath: config.GithubAppConf.SecretPath,
  352. GithubInstallationID: request.GitRepoID,
  353. GitRepoName: repoSplit[1],
  354. GitRepoOwner: repoSplit[0],
  355. Repo: config.Repo,
  356. ProjectID: projectID,
  357. ClusterID: clusterID,
  358. ReleaseName: name,
  359. ReleaseNamespace: namespace,
  360. GitBranch: request.GitBranch,
  361. DockerFilePath: request.DockerfilePath,
  362. FolderPath: request.FolderPath,
  363. ImageRepoURL: request.ImageRepoURI,
  364. PorterToken: encoded,
  365. Version: "v0.1.0",
  366. ShouldCreateWorkflow: request.ShouldCreateWorkflow,
  367. DryRun: release == nil,
  368. }
  369. // Save the github err for after creating the git action config. However, we
  370. // need to call Setup() in order to get the workflow file before writing the
  371. // action config, in the case of a dry run, since the dry run does not create
  372. // a git action config.
  373. workflowYAML, gitErr = gaRunner.Setup()
  374. if gaRunner.DryRun {
  375. if gitErr != nil {
  376. return nil, nil, telemetry.Error(ctx, span, gitErr, "error setting up git")
  377. }
  378. return nil, workflowYAML, nil
  379. }
  380. }
  381. // handle write to the database
  382. ga, err := config.Repo.GitActionConfig().CreateGitActionConfig(&models.GitActionConfig{
  383. ReleaseID: release.ID,
  384. GitRepo: request.GitRepo,
  385. GitBranch: request.GitBranch,
  386. ImageRepoURI: request.ImageRepoURI,
  387. GitRepoID: request.GitRepoID,
  388. GitlabIntegrationID: request.GitlabIntegrationID,
  389. DockerfilePath: request.DockerfilePath,
  390. FolderPath: request.FolderPath,
  391. IsInstallation: true,
  392. Version: "v0.1.0",
  393. })
  394. if err != nil {
  395. return nil, nil, telemetry.Error(ctx, span, err, "error creating git action config")
  396. }
  397. // update the release in the db with the image repo uri
  398. release.ImageRepoURI = ga.ImageRepoURI
  399. _, err = config.Repo.Release().UpdateRelease(release)
  400. if err != nil {
  401. return nil, nil, telemetry.Error(ctx, span, err, "error updating release")
  402. }
  403. return ga.ToGitActionConfigType(), workflowYAML, gitErr
  404. }
  405. func getToken(
  406. ctx context.Context,
  407. config *config.Config,
  408. userID, projectID, clusterID uint,
  409. request *types.CreateGitActionConfigRequest,
  410. ) (string, error) {
  411. ctx, span := telemetry.NewSpan(ctx, "get-git-action-token")
  412. defer span.End()
  413. telemetry.WithAttributes(span,
  414. telemetry.AttributeKV{Key: "project-id", Value: projectID},
  415. telemetry.AttributeKV{Key: "cluster-id", Value: clusterID},
  416. telemetry.AttributeKV{Key: "user-id", Value: userID},
  417. )
  418. // create a policy for the token
  419. policy := []*types.PolicyDocument{
  420. {
  421. Scope: types.ProjectScope,
  422. Verbs: types.ReadWriteVerbGroup(),
  423. Children: map[types.PermissionScope]*types.PolicyDocument{
  424. types.ClusterScope: {
  425. Scope: types.ClusterScope,
  426. Verbs: types.ReadWriteVerbGroup(),
  427. },
  428. types.RegistryScope: {
  429. Scope: types.RegistryScope,
  430. Verbs: types.ReadVerbGroup(),
  431. },
  432. types.HelmRepoScope: {
  433. Scope: types.HelmRepoScope,
  434. Verbs: types.ReadVerbGroup(),
  435. },
  436. },
  437. },
  438. }
  439. uid, err := encryption.GenerateRandomBytes(16)
  440. if err != nil {
  441. return "", telemetry.Error(ctx, span, err, "error generating uid")
  442. }
  443. policyBytes, err := json.Marshal(policy)
  444. if err != nil {
  445. return "", telemetry.Error(ctx, span, err, "error marshalling policy into json")
  446. }
  447. policyModel := &models.Policy{
  448. ProjectID: projectID,
  449. UniqueID: uid,
  450. CreatedByUserID: userID,
  451. Name: strings.ToLower(fmt.Sprintf("repo-%s-token-policy", request.GitRepo)),
  452. PolicyBytes: policyBytes,
  453. }
  454. policyModel, err = config.Repo.Policy().CreatePolicy(policyModel)
  455. if err != nil {
  456. return "", telemetry.Error(ctx, span, err, "error creating policy")
  457. }
  458. // create the token in the database
  459. tokenUID, err := encryption.GenerateRandomBytes(16)
  460. if err != nil {
  461. return "", telemetry.Error(ctx, span, err, "error generating tokenUID")
  462. }
  463. secretKey, err := encryption.GenerateRandomBytes(16)
  464. if err != nil {
  465. return "", telemetry.Error(ctx, span, err, "error generating secret key")
  466. }
  467. // hash the secret key for storage in the db
  468. hashedToken, err := bcrypt.GenerateFromPassword([]byte(secretKey), 8)
  469. if err != nil {
  470. return "", telemetry.Error(ctx, span, err, "error generating hashedToken")
  471. }
  472. expiresAt := time.Now().Add(time.Hour * 24 * 365)
  473. apiToken := &models.APIToken{
  474. UniqueID: tokenUID,
  475. ProjectID: projectID,
  476. CreatedByUserID: userID,
  477. Expiry: &expiresAt,
  478. Revoked: false,
  479. PolicyUID: policyModel.UniqueID,
  480. PolicyName: policyModel.Name,
  481. Name: strings.ToLower(fmt.Sprintf("repo-%s-token", request.GitRepo)),
  482. SecretKey: hashedToken,
  483. }
  484. apiToken, err = config.Repo.APIToken().CreateAPIToken(apiToken)
  485. if err != nil {
  486. return "", telemetry.Error(ctx, span, err, "error creating api token")
  487. }
  488. // generate porter jwt token
  489. jwt, err := token.GetStoredTokenForAPI(userID, projectID, apiToken.UniqueID, secretKey)
  490. if err != nil {
  491. return "", telemetry.Error(ctx, span, err, "error getting stored token for api")
  492. }
  493. return jwt.EncodeToken(config.TokenConf)
  494. }
  495. func createBuildConfig(
  496. ctx context.Context,
  497. config *config.Config,
  498. release *models.Release,
  499. bcRequest *types.CreateBuildConfigRequest,
  500. ) (*types.BuildConfig, error) {
  501. ctx, span := telemetry.NewSpan(ctx, "create-build-config")
  502. defer span.End()
  503. data, err := json.Marshal(bcRequest.Config)
  504. if err != nil {
  505. return nil, telemetry.Error(ctx, span, err, "error marshalling build config request")
  506. }
  507. // handle write to the database
  508. bc, err := config.Repo.BuildConfig().CreateBuildConfig(&models.BuildConfig{
  509. Builder: bcRequest.Builder,
  510. Buildpacks: strings.Join(bcRequest.Buildpacks, ","),
  511. Config: data,
  512. })
  513. if err != nil {
  514. return nil, telemetry.Error(ctx, span, err, "error creating build config")
  515. }
  516. release.BuildConfig = bc.ID
  517. _, err = config.Repo.Release().UpdateRelease(release)
  518. if err != nil {
  519. return nil, telemetry.Error(ctx, span, err, "error updating release")
  520. }
  521. return bc.ToBuildConfigType(), nil
  522. }
  523. type containerEnvConfig struct {
  524. Container struct {
  525. Env struct {
  526. Normal map[string]string `yaml:"normal"`
  527. } `yaml:"env"`
  528. } `yaml:"container"`
  529. }
  530. func GetGARunner(
  531. ctx context.Context,
  532. config *config.Config,
  533. userID, projectID, clusterID uint,
  534. ga *models.GitActionConfig,
  535. name, namespace string,
  536. release *models.Release,
  537. helmRelease *release.Release,
  538. ) (*actions.GithubActions, error) {
  539. ctx, span := telemetry.NewSpan(ctx, "get-ga-runner")
  540. defer span.End()
  541. cEnv := &containerEnvConfig{}
  542. rawValues, err := yaml.Marshal(helmRelease.Config)
  543. if err == nil {
  544. err = yaml.Unmarshal(rawValues, cEnv)
  545. // if unmarshal error, just set to empty map
  546. if err != nil {
  547. cEnv.Container.Env.Normal = make(map[string]string)
  548. }
  549. }
  550. repoSplit := strings.Split(ga.GitRepo, "/")
  551. if len(repoSplit) != 2 {
  552. return nil, telemetry.Error(ctx, span, nil, "invalid formatting of repo name")
  553. }
  554. // create the commit in the git repo
  555. return &actions.GithubActions{
  556. ServerURL: config.ServerConf.ServerURL,
  557. GithubOAuthIntegration: nil,
  558. BuildEnv: cEnv.Container.Env.Normal,
  559. GithubAppID: config.GithubAppConf.AppID,
  560. GithubAppSecretPath: config.GithubAppConf.SecretPath,
  561. GithubInstallationID: ga.GitRepoID,
  562. GitRepoName: repoSplit[1],
  563. GitRepoOwner: repoSplit[0],
  564. Repo: config.Repo,
  565. ProjectID: projectID,
  566. ClusterID: clusterID,
  567. ReleaseName: name,
  568. GitBranch: ga.GitBranch,
  569. DockerFilePath: ga.DockerfilePath,
  570. FolderPath: ga.FolderPath,
  571. ImageRepoURL: ga.ImageRepoURI,
  572. Version: "v0.1.0",
  573. }, nil
  574. }