install_agent.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. package cluster
  2. import (
  3. "context"
  4. "fmt"
  5. "net/http"
  6. "github.com/porter-dev/porter/api/server/authz"
  7. "github.com/porter-dev/porter/api/server/handlers"
  8. "github.com/porter-dev/porter/api/server/shared"
  9. "github.com/porter-dev/porter/api/server/shared/apierrors"
  10. "github.com/porter-dev/porter/api/server/shared/config"
  11. "github.com/porter-dev/porter/api/types"
  12. "github.com/porter-dev/porter/internal/auth/token"
  13. "github.com/porter-dev/porter/internal/helm"
  14. "github.com/porter-dev/porter/internal/helm/loader"
  15. "github.com/porter-dev/porter/internal/kubernetes"
  16. "github.com/porter-dev/porter/internal/kubernetes/nodes"
  17. "github.com/porter-dev/porter/internal/models"
  18. "github.com/porter-dev/porter/internal/telemetry"
  19. v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  20. )
  21. const (
  22. monitoringNodeLabel = "porter.run/workload-kind=monitoring"
  23. olderAgentLabel = "control-plane=controller-manager"
  24. )
  25. type InstallAgentHandler struct {
  26. handlers.PorterHandlerReadWriter
  27. authz.KubernetesAgentGetter
  28. }
  29. func NewInstallAgentHandler(
  30. config *config.Config,
  31. decoderValidator shared.RequestDecoderValidator,
  32. writer shared.ResultWriter,
  33. ) *InstallAgentHandler {
  34. return &InstallAgentHandler{
  35. PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
  36. KubernetesAgentGetter: authz.NewOutOfClusterAgentGetter(config),
  37. }
  38. }
  39. func (c *InstallAgentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  40. ctx, span := telemetry.NewSpan(r.Context(), "serve-install-agent-handler")
  41. defer span.End()
  42. proj, _ := ctx.Value(types.ProjectScope).(*models.Project)
  43. user, _ := ctx.Value(types.UserScope).(*models.User)
  44. cluster, _ := ctx.Value(types.ClusterScope).(*models.Cluster)
  45. k8sAgent, err := c.GetAgent(r, cluster, "porter-agent-system")
  46. if err != nil {
  47. err = telemetry.Error(ctx, span, err, "failed to get kubernetes agent")
  48. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  49. return
  50. }
  51. helmAgent, err := c.GetHelmAgent(ctx, r, cluster, "porter-agent-system")
  52. if err != nil {
  53. err = telemetry.Error(ctx, span, err, "failed to get helm agent")
  54. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  55. return
  56. }
  57. err = checkAndDeleteOlderAgent(k8sAgent, helmAgent)
  58. if err != nil {
  59. err = telemetry.Error(ctx, span, err, "unable to delete older agent")
  60. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  61. return
  62. }
  63. chart, err := loader.LoadChartPublic(ctx, c.Config().ServerConf.DefaultAddonHelmRepoURL, "porter-agent", "")
  64. if err != nil {
  65. err = telemetry.Error(ctx, span, err, "failed load public porter-agent chart")
  66. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  67. return
  68. }
  69. // create namespace if not exists
  70. _, err = helmAgent.K8sAgent.CreateNamespace("porter-agent-system", nil)
  71. if err != nil {
  72. err = telemetry.Error(ctx, span, err, "failed to get create porter-agent-system namespace")
  73. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  74. return
  75. }
  76. // add api token to values
  77. jwt, err := token.GetTokenForAPI(user.ID, proj.ID)
  78. if err != nil {
  79. err = telemetry.Error(ctx, span, err, "failed to get porter-agent api token")
  80. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  81. return
  82. }
  83. encoded, err := jwt.EncodeToken(c.Config().TokenConf)
  84. if err != nil {
  85. err = telemetry.Error(ctx, span, err, "failed to encode porter-agent api token")
  86. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  87. return
  88. }
  89. nodes, err := nodes.ListNodesByLabels(k8sAgent.Clientset, "porter.run/workload-kind=monitoring")
  90. hasMonitoringNodes := err == nil && len(nodes) >= 1
  91. porterAgentValues := map[string]interface{}{
  92. "agent": map[string]interface{}{
  93. "porterHost": c.Config().ServerConf.ServerURL,
  94. "porterPort": "443",
  95. "porterToken": encoded,
  96. "clusterID": fmt.Sprintf("%d", cluster.ID),
  97. "projectID": fmt.Sprintf("%d", proj.ID),
  98. "prometheusURL": c.Config().ServerConf.PrometheusUrl,
  99. "metronomeKey": c.Config().ServerConf.MetronomeAPIKey,
  100. },
  101. "loki": map[string]interface{}{},
  102. }
  103. // case on whether a node with porter.run/workload-kind=monitoring exists. If it does, we place loki in that node group.
  104. if hasMonitoringNodes {
  105. sharedNS := map[string]interface{}{
  106. "porter.run/workload-kind": "monitoring",
  107. }
  108. sharedTolerations := []map[string]interface{}{
  109. {
  110. "key": "porter.run/workload-kind",
  111. "operator": "Equal",
  112. "value": "monitoring",
  113. "effect": "NoSchedule",
  114. },
  115. }
  116. porterAgentValues["loki"] = map[string]interface{}{
  117. "nodeSelector": sharedNS,
  118. "tolerations": sharedTolerations,
  119. }
  120. porterAgentValues["nodeSelector"] = sharedNS
  121. porterAgentValues["tolerations"] = sharedTolerations
  122. }
  123. conf := &helm.InstallChartConfig{
  124. Chart: chart,
  125. Name: "porter-agent",
  126. Namespace: "porter-agent-system",
  127. Cluster: cluster,
  128. Repo: c.Repo(),
  129. Values: porterAgentValues,
  130. }
  131. _, err = helmAgent.InstallChart(context.Background(), conf, c.Config().DOConf, c.Config().ServerConf.DisablePullSecretsInjection)
  132. if err != nil {
  133. err = telemetry.Error(ctx, span, err, "error installing porter-agent")
  134. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  135. return
  136. }
  137. w.WriteHeader(http.StatusOK)
  138. }
  139. func checkAndDeleteOlderAgent(k8sAgent *kubernetes.Agent, helmAgent *helm.Agent) error {
  140. namespaceList, err := k8sAgent.Clientset.CoreV1().Namespaces().List(context.Background(), v1.ListOptions{})
  141. if err != nil {
  142. return fmt.Errorf("error listing namespaces: %w", err)
  143. }
  144. nsExists := false
  145. for _, namespace := range namespaceList.Items {
  146. if namespace.Name == "porter-agent-system" {
  147. nsExists = true
  148. break
  149. }
  150. }
  151. if !nsExists {
  152. return nil
  153. }
  154. // detect if the `porter-agent` release is installed
  155. helmRelease, err := helmAgent.GetRelease(context.Background(), "porter-agent", 0, false)
  156. if err != nil || helmRelease == nil {
  157. return nil
  158. }
  159. _, err = helmAgent.UninstallChart(context.Background(), "porter-agent")
  160. if err != nil {
  161. return err
  162. }
  163. return nil
  164. }