list.go 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. package environment_groups
  2. import (
  3. "context"
  4. "fmt"
  5. "strconv"
  6. "strings"
  7. "time"
  8. "github.com/porter-dev/porter/internal/kubernetes"
  9. "github.com/porter-dev/porter/internal/telemetry"
  10. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  11. )
  12. const (
  13. LabelKey_LinkedEnvironmentGroup = "porter.run/linked-environment-group"
  14. LabelKey_EnvironmentGroupVersion = "porter.run/environment-group-version"
  15. LabelKey_EnvironmentGroupName = "porter.run/environment-group-name"
  16. // Namespace_EnvironmentGroups is the base namespace for storing all environment groups.
  17. // The configmaps and secrets here should be considered the source's of truth for a given version
  18. Namespace_EnvironmentGroups = "porter-env-group"
  19. )
  20. // EnvironmentGroup represents a ConfigMap in the porter-env-group namespace
  21. type EnvironmentGroup struct {
  22. // Name is the environment group name which can be found in the labels (LabelKey_EnvironmentGroupName) of the ConfigMap. This is NOT the configmap name
  23. Name string `json:"name"`
  24. // Version is the environment group version which can be found in the labels (LabelKey_EnvironmentGroupVersion) of the ConfigMap. This is NOT included in the configmap name
  25. Version int `json:"latest_version"`
  26. // Variables are non-secret values for the EnvironmentGroup. This usually will be a configmap
  27. Variables map[string]string `json:"variables"`
  28. // SecretVariables are secret values for the EnvironmentGroup. This usually will be a Secret on the kubernetes cluster
  29. SecretVariables map[string][]byte `json:"variables_secrets"`
  30. // CreatedAt is only used for display purposes and is in UTC Unix time
  31. CreatedAtUTC time.Time `json:"created_at"`
  32. }
  33. type environmentGroupOptions struct {
  34. namespace string
  35. environmentGroupLabelName string
  36. environmentGroupLabelVersion int
  37. }
  38. // EnvironmentGroupOption is a function that modifies ListEnvironmentGroups
  39. type EnvironmentGroupOption func(*environmentGroupOptions)
  40. // WithNamespace filters all environment groups in a given namespace
  41. func WithNamespace(namespace string) EnvironmentGroupOption {
  42. return func(opts *environmentGroupOptions) {
  43. opts.namespace = namespace
  44. }
  45. }
  46. // WithEnvironmentGroupName filters all environment groups by name
  47. func WithEnvironmentGroupName(name string) EnvironmentGroupOption {
  48. return func(opts *environmentGroupOptions) {
  49. opts.environmentGroupLabelName = name
  50. }
  51. }
  52. // WithEnvironmentGroupVersion filters all environment groups by version
  53. func WithEnvironmentGroupVersion(version int) EnvironmentGroupOption {
  54. return func(opts *environmentGroupOptions) {
  55. opts.environmentGroupLabelVersion = version
  56. }
  57. }
  58. // ListEnvironmentGroups returns all environment groups stored in the provided namespace. If none is set, it will use the namespace "porter-env-group"
  59. func ListEnvironmentGroups(ctx context.Context, a *kubernetes.Agent, listOpts ...EnvironmentGroupOption) ([]EnvironmentGroup, error) {
  60. ctx, span := telemetry.NewSpan(ctx, "list-environment-groups")
  61. defer span.End()
  62. var opts environmentGroupOptions
  63. for _, opt := range listOpts {
  64. opt(&opts)
  65. }
  66. if opts.namespace == "" {
  67. opts.namespace = Namespace_EnvironmentGroups
  68. }
  69. var labelSelectors []string
  70. if opts.environmentGroupLabelName != "" {
  71. labelSelectors = append(labelSelectors, fmt.Sprintf("%s=%s", LabelKey_EnvironmentGroupName, opts.environmentGroupLabelName))
  72. }
  73. if opts.environmentGroupLabelVersion != 0 {
  74. labelSelectors = append(labelSelectors, fmt.Sprintf("%s=%d", LabelKey_EnvironmentGroupVersion, opts.environmentGroupLabelVersion))
  75. }
  76. labelSelector := strings.Join(labelSelectors, ",")
  77. listOptions := metav1.ListOptions{
  78. LabelSelector: labelSelector,
  79. }
  80. telemetry.WithAttributes(span,
  81. telemetry.AttributeKV{Key: "namespace", Value: opts.namespace},
  82. telemetry.AttributeKV{Key: "label-selector", Value: labelSelector},
  83. )
  84. configMapListResp, err := a.Clientset.CoreV1().ConfigMaps(opts.namespace).List(ctx, listOptions)
  85. if err != nil {
  86. return nil, telemetry.Error(ctx, span, err, "unable to list environment group variables")
  87. }
  88. secretListResp, err := a.Clientset.CoreV1().Secrets(opts.namespace).List(ctx, listOptions)
  89. if err != nil {
  90. return nil, telemetry.Error(ctx, span, err, "unable to list environment groups secret varialbes")
  91. }
  92. // envGroupSet's key is the environment group's versioned name
  93. envGroupSet := make(map[string]EnvironmentGroup)
  94. for _, cm := range configMapListResp.Items {
  95. name, ok := cm.Labels[LabelKey_EnvironmentGroupName]
  96. if !ok {
  97. continue // missing name label, not an environment group
  98. }
  99. versionString, ok := cm.Labels[LabelKey_EnvironmentGroupVersion]
  100. if !ok {
  101. continue // missing version label, not an environment group
  102. }
  103. version, err := strconv.Atoi(versionString)
  104. if err != nil {
  105. continue // invalid version label as it should be an int, not an environment group
  106. }
  107. if _, ok := envGroupSet[cm.Name]; !ok {
  108. envGroupSet[cm.Name] = EnvironmentGroup{}
  109. }
  110. envGroupSet[cm.Name] = EnvironmentGroup{
  111. Name: name,
  112. Version: version,
  113. Variables: cm.Data,
  114. SecretVariables: envGroupSet[cm.Name].SecretVariables,
  115. CreatedAtUTC: cm.CreationTimestamp.Time.UTC(),
  116. }
  117. }
  118. for _, secret := range secretListResp.Items {
  119. name, ok := secret.Labels[LabelKey_EnvironmentGroupName]
  120. if !ok {
  121. continue // missing name label, not an environment group
  122. }
  123. versionString, ok := secret.Labels[LabelKey_EnvironmentGroupVersion]
  124. if !ok {
  125. continue // missing version label, not an environment group
  126. }
  127. version, err := strconv.Atoi(versionString)
  128. if err != nil {
  129. continue // invalid version label as it should be an int, not an environment group
  130. }
  131. if _, ok := envGroupSet[secret.Name]; !ok {
  132. envGroupSet[secret.Name] = EnvironmentGroup{}
  133. }
  134. envGroupSet[secret.Name] = EnvironmentGroup{
  135. Name: name,
  136. Version: version,
  137. SecretVariables: secret.Data,
  138. Variables: envGroupSet[secret.Name].Variables,
  139. CreatedAtUTC: secret.CreationTimestamp.Time.UTC(),
  140. }
  141. }
  142. var envGroups []EnvironmentGroup
  143. for _, envGroup := range envGroupSet {
  144. envGroups = append(envGroups, envGroup)
  145. }
  146. return envGroups, nil
  147. }
  148. // LinkedPorterApplication represents an application which was linked to an environment group
  149. type LinkedPorterApplication struct {
  150. Name string
  151. Namespace string
  152. }
  153. // LinkedApplications lists all applications that are linked to a given environment group. Since there can be multiple linked environment groups we must check by the presence of a label on the deployment and job
  154. func LinkedApplications(ctx context.Context, a *kubernetes.Agent, environmentGroupName string) ([]LinkedPorterApplication, error) {
  155. ctx, span := telemetry.NewSpan(ctx, "list-linked-applications")
  156. defer span.End()
  157. if environmentGroupName == "" {
  158. return nil, telemetry.Error(ctx, span, nil, "environment group cannot be empty")
  159. }
  160. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "environment-group-name", Value: environmentGroupName})
  161. deployListResp, err := a.Clientset.AppsV1().Deployments(metav1.NamespaceAll).List(ctx,
  162. metav1.ListOptions{
  163. LabelSelector: LabelKey_LinkedEnvironmentGroup,
  164. })
  165. if err != nil {
  166. return nil, telemetry.Error(ctx, span, err, "unable to list linked deployment applications")
  167. }
  168. var apps []LinkedPorterApplication
  169. for _, d := range deployListResp.Items {
  170. applicationsLinkedEnvironmentGroups := strings.Split(d.Labels[LabelKey_LinkedEnvironmentGroup], ".")
  171. for _, linkedEnvironmentGroup := range applicationsLinkedEnvironmentGroups {
  172. if linkedEnvironmentGroup == environmentGroupName {
  173. apps = append(apps, LinkedPorterApplication{
  174. Name: d.Name,
  175. Namespace: d.Namespace,
  176. })
  177. }
  178. }
  179. }
  180. cronListResp, err := a.Clientset.BatchV1().CronJobs(metav1.NamespaceAll).List(ctx,
  181. metav1.ListOptions{
  182. LabelSelector: LabelKey_LinkedEnvironmentGroup,
  183. })
  184. if err != nil {
  185. return nil, telemetry.Error(ctx, span, err, "unable to list linked cronjob applications")
  186. }
  187. for _, d := range cronListResp.Items {
  188. applicationsLinkedEnvironmentGroups := strings.Split(d.Labels[LabelKey_LinkedEnvironmentGroup], ".")
  189. for _, linkedEnvironmentGroup := range applicationsLinkedEnvironmentGroups {
  190. if linkedEnvironmentGroup == environmentGroupName {
  191. apps = append(apps, LinkedPorterApplication{
  192. Name: d.Name,
  193. Namespace: d.Namespace,
  194. })
  195. }
  196. }
  197. }
  198. return apps, nil
  199. }