install_agent.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  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. v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  19. )
  20. const (
  21. monitoringNodeLabel = "porter.run/workload-kind=monitoring"
  22. olderAgentLabel = "control-plane=controller-manager"
  23. )
  24. type InstallAgentHandler struct {
  25. handlers.PorterHandlerReadWriter
  26. authz.KubernetesAgentGetter
  27. }
  28. func NewInstallAgentHandler(
  29. config *config.Config,
  30. decoderValidator shared.RequestDecoderValidator,
  31. writer shared.ResultWriter,
  32. ) *InstallAgentHandler {
  33. return &InstallAgentHandler{
  34. PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
  35. KubernetesAgentGetter: authz.NewOutOfClusterAgentGetter(config),
  36. }
  37. }
  38. func (c *InstallAgentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  39. proj, _ := r.Context().Value(types.ProjectScope).(*models.Project)
  40. user, _ := r.Context().Value(types.UserScope).(*models.User)
  41. cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
  42. k8sAgent, err := c.GetAgent(r, cluster, "porter-agent-system")
  43. if err != nil {
  44. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  45. return
  46. }
  47. err = checkAndDeleteOlderAgent(k8sAgent)
  48. if err != nil {
  49. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  50. return
  51. }
  52. helmAgent, err := c.GetHelmAgent(r, cluster, "porter-agent-system")
  53. if err != nil {
  54. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  55. return
  56. }
  57. chart, err := loader.LoadChartPublic(c.Config().ServerConf.DefaultAddonHelmRepoURL, "porter-agent", "")
  58. if err != nil {
  59. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  60. return
  61. }
  62. // create namespace if not exists
  63. _, err = helmAgent.K8sAgent.CreateNamespace("porter-agent-system")
  64. if err != nil {
  65. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  66. return
  67. }
  68. // add api token to values
  69. jwt, err := token.GetTokenForAPI(user.ID, proj.ID)
  70. if err != nil {
  71. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  72. return
  73. }
  74. encoded, err := jwt.EncodeToken(c.Config().TokenConf)
  75. if err != nil {
  76. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  77. return
  78. }
  79. lokiValues := make(map[string]interface{})
  80. // case on whether a node with porter.run/workload-kind=monitoring exists. If it does, we place loki in that node group.
  81. if nodes, err := nodes.ListNodesByLabels(k8sAgent.Clientset, "porter.run/workload-kind=monitoring"); err == nil && len(nodes) >= 1 {
  82. lokiValues = map[string]interface{}{
  83. "nodeSelector": map[string]interface{}{
  84. "porter.run/workload-kind": "monitoring",
  85. },
  86. "tolerations": []map[string]interface{}{
  87. {
  88. "key": "porter.run/workload-kind",
  89. "operator": "Equal",
  90. "value": "monitoring",
  91. "effect": "NoSchedule",
  92. },
  93. },
  94. }
  95. }
  96. porterAgentValues := map[string]interface{}{
  97. "agent": map[string]interface{}{
  98. "porterHost": c.Config().ServerConf.ServerURL,
  99. "porterPort": "443",
  100. "porterToken": encoded,
  101. "clusterID": fmt.Sprintf("%d", cluster.ID),
  102. "projectID": fmt.Sprintf("%d", proj.ID),
  103. },
  104. "loki": lokiValues,
  105. }
  106. conf := &helm.InstallChartConfig{
  107. Chart: chart,
  108. Name: "porter-agent",
  109. Namespace: "porter-agent-system",
  110. Cluster: cluster,
  111. Repo: c.Repo(),
  112. Values: porterAgentValues,
  113. }
  114. _, err = helmAgent.InstallChart(conf, c.Config().DOConf)
  115. if err != nil {
  116. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
  117. fmt.Errorf("error installing porter-agent: %w", err), http.StatusBadRequest,
  118. ))
  119. return
  120. }
  121. w.WriteHeader(http.StatusOK)
  122. }
  123. func checkAndDeleteOlderAgent(k8sAgent *kubernetes.Agent) error {
  124. namespaceList, err := k8sAgent.Clientset.CoreV1().Namespaces().List(context.Background(), v1.ListOptions{})
  125. if err != nil {
  126. return fmt.Errorf("error listing namespaces: %w", err)
  127. }
  128. nsExists := false
  129. for _, namespace := range namespaceList.Items {
  130. if namespace.Name == "porter-agent-system" {
  131. nsExists = true
  132. break
  133. }
  134. }
  135. if !nsExists {
  136. return nil
  137. }
  138. podList, err := k8sAgent.Clientset.CoreV1().Pods("porter-agent-system").List(context.Background(), v1.ListOptions{
  139. LabelSelector: olderAgentLabel,
  140. })
  141. if err != nil {
  142. return fmt.Errorf("error listing pods for older porter-agent: %w", err)
  143. }
  144. if len(podList.Items) > 0 {
  145. // older porter-agent exists, delete the entire namespace
  146. err := k8sAgent.Clientset.CoreV1().Namespaces().Delete(
  147. context.Background(), "porter-agent-system", v1.DeleteOptions{},
  148. )
  149. if err != nil {
  150. return fmt.Errorf("error deleting older porter-agent's namespace: %w", err)
  151. }
  152. }
  153. return nil
  154. }