agent.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649
  1. package helm
  2. import (
  3. "bytes"
  4. "context"
  5. "fmt"
  6. "runtime/debug"
  7. "strconv"
  8. "strings"
  9. "time"
  10. "github.com/porter-dev/api-contracts/generated/go/porter/v1/porterv1connect"
  11. "github.com/porter-dev/porter/internal/telemetry"
  12. "github.com/pkg/errors"
  13. "github.com/porter-dev/porter/internal/helm/loader"
  14. "github.com/stefanmcshane/helm/pkg/action"
  15. "github.com/stefanmcshane/helm/pkg/chart"
  16. "github.com/stefanmcshane/helm/pkg/release"
  17. "github.com/stefanmcshane/helm/pkg/storage/driver"
  18. "golang.org/x/oauth2"
  19. corev1 "k8s.io/api/core/v1"
  20. v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  21. "k8s.io/helm/pkg/chartutil"
  22. "github.com/porter-dev/porter/api/types"
  23. "github.com/porter-dev/porter/internal/kubernetes"
  24. "github.com/porter-dev/porter/internal/models"
  25. "github.com/porter-dev/porter/internal/repository"
  26. )
  27. // Agent is a Helm agent for performing helm operations
  28. type Agent struct {
  29. ActionConfig *action.Configuration
  30. K8sAgent *kubernetes.Agent
  31. }
  32. // ListReleases lists releases based on a ListFilter
  33. func (a *Agent) ListReleases(
  34. ctx context.Context,
  35. namespace string,
  36. filter *types.ReleaseListFilter,
  37. ) ([]*release.Release, error) {
  38. ctx, span := telemetry.NewSpan(ctx, "helm-list-releases")
  39. defer span.End()
  40. telemetry.WithAttributes(span,
  41. telemetry.AttributeKV{Key: "namespace", Value: namespace},
  42. )
  43. lsel := fmt.Sprintf("owner=helm,status in (%s)", strings.Join(filter.StatusFilter, ","))
  44. // list secrets
  45. secretList, err := a.K8sAgent.Clientset.CoreV1().Secrets(namespace).List(
  46. context.Background(),
  47. v1.ListOptions{
  48. LabelSelector: lsel,
  49. },
  50. )
  51. if err != nil {
  52. return nil, telemetry.Error(ctx, span, err, "error getting secret list")
  53. }
  54. // before decoding to helm release, only keep the latest releases for each chart
  55. latestMap := make(map[string]corev1.Secret)
  56. for _, secret := range secretList.Items {
  57. relName, relNameExists := secret.Labels["name"]
  58. if !relNameExists {
  59. continue
  60. }
  61. id := fmt.Sprintf("%s/%s", secret.Namespace, relName)
  62. if currLatest, exists := latestMap[id]; exists {
  63. // get version
  64. currVersionStr, currVersionExists := currLatest.Labels["version"]
  65. versionStr, versionExists := secret.Labels["version"]
  66. if versionExists && currVersionExists {
  67. currVersion, currErr := strconv.Atoi(currVersionStr)
  68. version, err := strconv.Atoi(versionStr)
  69. if currErr == nil && err == nil && currVersion < version {
  70. latestMap[id] = secret
  71. }
  72. }
  73. } else {
  74. latestMap[id] = secret
  75. }
  76. }
  77. chartList := []string{}
  78. res := make([]*release.Release, 0)
  79. for _, secret := range latestMap {
  80. rel, isErr, err := kubernetes.ParseSecretToHelmRelease(secret, chartList)
  81. if !isErr && err == nil {
  82. res = append(res, rel)
  83. }
  84. }
  85. return res, nil
  86. }
  87. // GetRelease returns the info of a release.
  88. func (a *Agent) GetRelease(
  89. ctx context.Context,
  90. name string,
  91. version int,
  92. getDeps bool,
  93. ) (*release.Release, error) {
  94. ctx, span := telemetry.NewSpan(ctx, "helm-get-release")
  95. defer span.End()
  96. telemetry.WithAttributes(span,
  97. telemetry.AttributeKV{Key: "name", Value: name},
  98. telemetry.AttributeKV{Key: "version", Value: version},
  99. telemetry.AttributeKV{Key: "getDeps", Value: getDeps},
  100. )
  101. // Namespace is already known by the RESTClientGetter.
  102. cmd := action.NewGet(a.ActionConfig)
  103. cmd.Version = version
  104. release, err := cmd.Run(name)
  105. if err != nil {
  106. return nil, telemetry.Error(ctx, span, err, "error running get release")
  107. }
  108. if getDeps && release.Chart != nil && release.Chart.Metadata != nil {
  109. for _, dep := range release.Chart.Metadata.Dependencies {
  110. // only search for dependency if it passes the condition specified in Chart.yaml
  111. if dep.Enabled {
  112. depExists := false
  113. for _, currDep := range release.Chart.Dependencies() {
  114. // we just case on name for now -- there might be edge cases we're missing
  115. // but this will cover 99% of cases
  116. if dep != nil && currDep != nil && dep.Name == currDep.Name() {
  117. depExists = true
  118. break
  119. }
  120. }
  121. if !depExists {
  122. depChart, err := loader.LoadChartPublic(ctx, dep.Repository, dep.Name, dep.Version)
  123. if err != nil {
  124. return nil, telemetry.Error(ctx, span, err, fmt.Sprintf("Error retrieving chart dependency %s/%s-%s", dep.Repository, dep.Name, dep.Version))
  125. }
  126. release.Chart.AddDependency(depChart)
  127. }
  128. }
  129. }
  130. }
  131. return release, err
  132. }
  133. // DeleteReleaseRevision deletes a specific revision of a release
  134. func (a *Agent) DeleteReleaseRevision(
  135. ctx context.Context,
  136. name string,
  137. version int,
  138. ) error {
  139. ctx, span := telemetry.NewSpan(ctx, "helm-delete-release-history")
  140. defer span.End()
  141. telemetry.WithAttributes(span,
  142. telemetry.AttributeKV{Key: "name", Value: name},
  143. telemetry.AttributeKV{Key: "version", Value: version},
  144. )
  145. _, err := a.ActionConfig.Releases.Delete(name, version)
  146. return err
  147. }
  148. // GetReleaseHistory returns a list of charts for a specific release
  149. func (a *Agent) GetReleaseHistory(
  150. ctx context.Context,
  151. name string,
  152. ) ([]*release.Release, error) {
  153. ctx, span := telemetry.NewSpan(ctx, "helm-get-release-history")
  154. defer span.End()
  155. telemetry.WithAttributes(span,
  156. telemetry.AttributeKV{Key: "name", Value: name},
  157. )
  158. cmd := action.NewHistory(a.ActionConfig)
  159. return cmd.Run(name)
  160. }
  161. type UpgradeReleaseConfig struct {
  162. Name string
  163. Values map[string]interface{}
  164. Cluster *models.Cluster
  165. Repo repository.Repository
  166. Registries []*models.Registry
  167. ClusterControlPlaneClient porterv1connect.ClusterControlPlaneServiceClient
  168. // Optional, if chart should be overriden
  169. Chart *chart.Chart
  170. // Optional, if chart is part of a Porter Stack
  171. StackName string
  172. StackRevision uint
  173. }
  174. // UpgradeRelease upgrades a specific release with new values.yaml
  175. func (a *Agent) UpgradeRelease(
  176. ctx context.Context,
  177. conf *UpgradeReleaseConfig,
  178. values string,
  179. doAuth *oauth2.Config,
  180. disablePullSecretsInjection bool,
  181. ignoreDependencies bool,
  182. ) (*release.Release, error) {
  183. ctx, span := telemetry.NewSpan(ctx, "helm-upgrade-release")
  184. defer span.End()
  185. telemetry.WithAttributes(span,
  186. telemetry.AttributeKV{Key: "project-id", Value: conf.Cluster.ProjectID},
  187. telemetry.AttributeKV{Key: "cluster-id", Value: conf.Cluster.ID},
  188. telemetry.AttributeKV{Key: "name", Value: conf.Name},
  189. telemetry.AttributeKV{Key: "stack-name", Value: conf.StackName},
  190. telemetry.AttributeKV{Key: "stack-revision", Value: conf.StackRevision},
  191. )
  192. valuesYaml, err := chartutil.ReadValues([]byte(values))
  193. if err != nil {
  194. return nil, telemetry.Error(ctx, span, err, "Values could not be parsed")
  195. }
  196. conf.Values = valuesYaml
  197. return a.UpgradeReleaseByValues(ctx, conf, doAuth, disablePullSecretsInjection, ignoreDependencies)
  198. }
  199. // UpgradeReleaseByValues upgrades a release by unmarshaled yaml values
  200. func (a *Agent) UpgradeReleaseByValues(
  201. ctx context.Context,
  202. conf *UpgradeReleaseConfig,
  203. doAuth *oauth2.Config,
  204. disablePullSecretsInjection bool,
  205. ignoreDependencies bool,
  206. ) (*release.Release, error) {
  207. ctx, span := telemetry.NewSpan(ctx, "helm-upgrade-release-by-values")
  208. defer span.End()
  209. telemetry.WithAttributes(span,
  210. telemetry.AttributeKV{Key: "project-id", Value: conf.Cluster.ProjectID},
  211. telemetry.AttributeKV{Key: "cluster-id", Value: conf.Cluster.ID},
  212. telemetry.AttributeKV{Key: "name", Value: conf.Name},
  213. telemetry.AttributeKV{Key: "stack-name", Value: conf.StackName},
  214. telemetry.AttributeKV{Key: "stack-revision", Value: conf.StackRevision},
  215. )
  216. // grab the latest release
  217. rel, err := a.GetRelease(ctx, conf.Name, 0, !ignoreDependencies)
  218. if err != nil {
  219. return nil, telemetry.Error(ctx, span, err, "Could not get release to be upgraded")
  220. }
  221. ch := rel.Chart
  222. if conf.Chart != nil {
  223. ch = conf.Chart
  224. }
  225. cmd := action.NewUpgrade(a.ActionConfig)
  226. cmd.Namespace = rel.Namespace
  227. cmd.PostRenderer, err = NewPorterPostrenderer(
  228. conf.Cluster,
  229. conf.Repo,
  230. a.K8sAgent,
  231. rel.Namespace,
  232. conf.Registries,
  233. doAuth,
  234. disablePullSecretsInjection,
  235. conf.ClusterControlPlaneClient,
  236. )
  237. if err != nil {
  238. return nil, telemetry.Error(ctx, span, err, "error getting porter postrenderer")
  239. }
  240. if conf.StackName != "" && conf.StackRevision > 0 {
  241. conf.Values["stack"] = map[string]interface{}{
  242. "enabled": true,
  243. "name": conf.StackName,
  244. "revision": conf.StackRevision,
  245. }
  246. }
  247. res, err := cmd.Run(conf.Name, ch, conf.Values)
  248. if err != nil {
  249. // refer: https://github.com/helm/helm/blob/release-3.8/pkg/action/action.go#L62
  250. // issue tracker: https://github.com/helm/helm/issues/4558
  251. if err.Error() == "another operation (install/upgrade/rollback) is in progress" {
  252. secretList, err := a.K8sAgent.Clientset.CoreV1().Secrets(rel.Namespace).List(
  253. context.Background(),
  254. v1.ListOptions{
  255. LabelSelector: fmt.Sprintf("owner=helm,status in (pending-install, pending-upgrade, pending-rollback),name=%s", rel.Name),
  256. },
  257. )
  258. if err != nil {
  259. return nil, telemetry.Error(ctx, span, err, "error getting secret list")
  260. }
  261. if len(secretList.Items) > 0 {
  262. mostRecentSecret := secretList.Items[0]
  263. for i := 1; i < len(secretList.Items); i += 1 {
  264. oldVersion, _ := strconv.Atoi(mostRecentSecret.Labels["version"])
  265. newVersion, _ := strconv.Atoi(secretList.Items[i].Labels["version"])
  266. if oldVersion < newVersion {
  267. mostRecentSecret = secretList.Items[i]
  268. }
  269. }
  270. if time.Since(mostRecentSecret.CreationTimestamp.Time) >= time.Minute {
  271. helmSecrets := driver.NewSecrets(a.K8sAgent.Clientset.CoreV1().Secrets(rel.Namespace))
  272. rel.Info.Status = release.StatusFailed
  273. err = helmSecrets.Update(mostRecentSecret.GetName(), rel)
  274. if err != nil {
  275. return nil, telemetry.Error(ctx, span, err, "error updating helm secrets")
  276. }
  277. // retry upgrade
  278. res, err = cmd.Run(conf.Name, ch, conf.Values)
  279. if err != nil {
  280. return nil, telemetry.Error(ctx, span, err, "error running upgrade after updating helm secrets")
  281. }
  282. return res, nil
  283. } else {
  284. // ask the user to wait for about a minute before retrying for the above fix to kick in
  285. return nil, telemetry.Error(ctx, span, err, "another operation (install/upgrade/rollback) is in progress. If this error persists, please wait for 60 seconds to force an upgrade")
  286. }
  287. }
  288. } else if strings.Contains(err.Error(), "current release manifest contains removed kubernetes api(s)") || strings.Contains(err.Error(), "resource mapping not found for name") {
  289. // ref: https://helm.sh/docs/topics/kubernetes_apis/#updating-api-versions-of-a-release-manifest
  290. // in this case, we manually update the secret containing the new manifests
  291. secretList, err := a.K8sAgent.Clientset.CoreV1().Secrets(rel.Namespace).List(
  292. context.Background(),
  293. v1.ListOptions{
  294. LabelSelector: fmt.Sprintf("owner=helm,name=%s", rel.Name),
  295. },
  296. )
  297. if err != nil {
  298. return nil, telemetry.Error(ctx, span, err, "error getting secret list")
  299. }
  300. if len(secretList.Items) > 0 {
  301. mostRecentSecret := secretList.Items[0]
  302. for i := 1; i < len(secretList.Items); i += 1 {
  303. oldVersion, _ := strconv.Atoi(mostRecentSecret.Labels["version"])
  304. newVersion, _ := strconv.Atoi(secretList.Items[i].Labels["version"])
  305. if oldVersion < newVersion {
  306. mostRecentSecret = secretList.Items[i]
  307. }
  308. }
  309. // run the equivalent of `helm template` to get the manifest string for the new release
  310. installCmd := action.NewInstall(a.ActionConfig)
  311. installCmd.ReleaseName = conf.Name
  312. installCmd.Namespace = rel.Namespace
  313. installCmd.DryRun = true
  314. installCmd.Replace = true
  315. installCmd.ClientOnly = false
  316. installCmd.IncludeCRDs = true
  317. newRelDryRun, err := installCmd.Run(ch, conf.Values)
  318. if err != nil {
  319. return nil, telemetry.Error(ctx, span, err, "error running install cmd")
  320. }
  321. oldManifestBuffer := bytes.NewBufferString(rel.Manifest)
  322. newManifestBuffer := bytes.NewBufferString(newRelDryRun.Manifest)
  323. versionMapper := &DeprecatedAPIVersionMapper{}
  324. updatedManifestBuffer, err := versionMapper.Run(oldManifestBuffer, newManifestBuffer)
  325. if err != nil {
  326. return nil, telemetry.Error(ctx, span, err, "error running version mapper")
  327. }
  328. rel.Manifest = updatedManifestBuffer.String()
  329. helmSecrets := driver.NewSecrets(a.K8sAgent.Clientset.CoreV1().Secrets(rel.Namespace))
  330. err = helmSecrets.Update(mostRecentSecret.GetName(), rel)
  331. if err != nil {
  332. return nil, telemetry.Error(ctx, span, err, "error updating helm secret")
  333. }
  334. res, err := cmd.Run(conf.Name, ch, conf.Values)
  335. if err != nil {
  336. return nil, telemetry.Error(ctx, span, err, "error running upgrade after updating helm secrets")
  337. }
  338. return res, nil
  339. }
  340. }
  341. return nil, telemetry.Error(ctx, span, err, "error running upgrade")
  342. }
  343. return res, nil
  344. }
  345. // InstallChartConfig is the config required to install a chart
  346. type InstallChartConfig struct {
  347. Chart *chart.Chart
  348. Name string
  349. Namespace string
  350. Values map[string]interface{}
  351. Cluster *models.Cluster
  352. Repo repository.Repository
  353. Registries []*models.Registry
  354. ClusterControlPlaneClient porterv1connect.ClusterControlPlaneServiceClient
  355. }
  356. // InstallChartFromValuesBytes reads the raw values and calls Agent.InstallChart
  357. func (a *Agent) InstallChartFromValuesBytes(
  358. ctx context.Context,
  359. conf *InstallChartConfig,
  360. values []byte,
  361. doAuth *oauth2.Config,
  362. disablePullSecretsInjection bool,
  363. ) (*release.Release, error) {
  364. ctx, span := telemetry.NewSpan(ctx, "helm-install-chart-from-values-bytes")
  365. defer span.End()
  366. telemetry.WithAttributes(span,
  367. telemetry.AttributeKV{Key: "project-id", Value: conf.Cluster.ProjectID},
  368. telemetry.AttributeKV{Key: "cluster-id", Value: conf.Cluster.ID},
  369. telemetry.AttributeKV{Key: "chart-name", Value: conf.Name},
  370. telemetry.AttributeKV{Key: "chart-namespace", Value: conf.Namespace},
  371. )
  372. valuesYaml, err := chartutil.ReadValues(values)
  373. if err != nil {
  374. return nil, telemetry.Error(ctx, span, err, "Values could not be parsed")
  375. }
  376. conf.Values = valuesYaml
  377. return a.InstallChart(ctx, conf, doAuth, disablePullSecretsInjection)
  378. }
  379. // InstallChart installs a new chart
  380. func (a *Agent) InstallChart(
  381. ctx context.Context,
  382. conf *InstallChartConfig,
  383. doAuth *oauth2.Config,
  384. disablePullSecretsInjection bool,
  385. ) (*release.Release, error) {
  386. defer func() {
  387. if r := recover(); r != nil {
  388. fmt.Println("stacktrace from panic: \n" + string(debug.Stack()))
  389. }
  390. }()
  391. ctx, span := telemetry.NewSpan(ctx, "helm-install-chart")
  392. defer span.End()
  393. telemetry.WithAttributes(span,
  394. telemetry.AttributeKV{Key: "project-id", Value: conf.Cluster.ProjectID},
  395. telemetry.AttributeKV{Key: "cluster-id", Value: conf.Cluster.ID},
  396. telemetry.AttributeKV{Key: "chart-name", Value: conf.Name},
  397. telemetry.AttributeKV{Key: "chart-namespace", Value: conf.Namespace},
  398. )
  399. cmd := action.NewInstall(a.ActionConfig)
  400. if cmd.Version == "" && cmd.Devel {
  401. cmd.Version = ">0.0.0-0"
  402. }
  403. cmd.ReleaseName = conf.Name
  404. cmd.Namespace = conf.Namespace
  405. cmd.Timeout = 300 * time.Second
  406. if err := checkIfInstallable(conf.Chart); err != nil {
  407. return nil, telemetry.Error(ctx, span, err, "error checking if installable")
  408. }
  409. var err error
  410. cmd.PostRenderer, err = NewPorterPostrenderer(
  411. conf.Cluster,
  412. conf.Repo,
  413. a.K8sAgent,
  414. conf.Namespace,
  415. conf.Registries,
  416. doAuth,
  417. disablePullSecretsInjection,
  418. conf.ClusterControlPlaneClient,
  419. )
  420. if err != nil {
  421. return nil, telemetry.Error(ctx, span, err, "error getting post renderer")
  422. }
  423. if req := conf.Chart.Metadata.Dependencies; req != nil {
  424. for _, dep := range req {
  425. depChart, err := loader.LoadChartPublic(ctx, dep.Repository, dep.Name, dep.Version)
  426. if err != nil {
  427. return nil, telemetry.Error(ctx, span, err, fmt.Sprintf("error retrieving chart dependency %s/%s-%s", dep.Repository, dep.Name, dep.Version))
  428. }
  429. conf.Chart.AddDependency(depChart)
  430. }
  431. }
  432. return cmd.Run(conf.Chart, conf.Values)
  433. }
  434. // UpgradeInstallChart installs a new chart if it doesn't exist, otherwise it upgrades it
  435. func (a *Agent) UpgradeInstallChart(
  436. ctx context.Context,
  437. conf *InstallChartConfig,
  438. doAuth *oauth2.Config,
  439. disablePullSecretsInjection bool,
  440. ) (*release.Release, error) {
  441. defer func() {
  442. if r := recover(); r != nil {
  443. fmt.Println("stacktrace from panic: \n" + string(debug.Stack()))
  444. }
  445. }()
  446. ctx, span := telemetry.NewSpan(ctx, "helm-upgrade-install-chart")
  447. defer span.End()
  448. telemetry.WithAttributes(span,
  449. telemetry.AttributeKV{Key: "project-id", Value: conf.Cluster.ProjectID},
  450. telemetry.AttributeKV{Key: "cluster-id", Value: conf.Cluster.ID},
  451. telemetry.AttributeKV{Key: "chart-name", Value: conf.Name},
  452. telemetry.AttributeKV{Key: "chart-namespace", Value: conf.Namespace},
  453. )
  454. cmd := action.NewUpgrade(a.ActionConfig)
  455. cmd.Install = true
  456. if cmd.Version == "" && cmd.Devel {
  457. cmd.Version = ">0.0.0-0"
  458. }
  459. cmd.Namespace = conf.Namespace
  460. cmd.Timeout = 300 * time.Second
  461. if err := checkIfInstallable(conf.Chart); err != nil {
  462. return nil, telemetry.Error(ctx, span, err, "error checking if installable")
  463. }
  464. var err error
  465. cmd.PostRenderer, err = NewPorterPostrenderer(
  466. conf.Cluster,
  467. conf.Repo,
  468. a.K8sAgent,
  469. conf.Namespace,
  470. conf.Registries,
  471. doAuth,
  472. disablePullSecretsInjection,
  473. conf.ClusterControlPlaneClient,
  474. )
  475. if err != nil {
  476. return nil, telemetry.Error(ctx, span, err, "error getting post renderer")
  477. }
  478. if req := conf.Chart.Metadata.Dependencies; req != nil {
  479. for _, dep := range req {
  480. depChart, err := loader.LoadChartPublic(ctx, dep.Repository, dep.Name, dep.Version)
  481. if err != nil {
  482. return nil, telemetry.Error(ctx, span, err, fmt.Sprintf("error retrieving chart dependency %s/%s-%s", dep.Repository, dep.Name, dep.Version))
  483. }
  484. conf.Chart.AddDependency(depChart)
  485. }
  486. }
  487. return cmd.Run(conf.Name, conf.Chart, conf.Values)
  488. }
  489. // UninstallChart uninstalls a chart
  490. func (a *Agent) UninstallChart(
  491. ctx context.Context,
  492. name string,
  493. ) (*release.UninstallReleaseResponse, error) {
  494. ctx, span := telemetry.NewSpan(ctx, "helm-uninstall-chart")
  495. defer span.End()
  496. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "chart-name", Value: name})
  497. cmd := action.NewUninstall(a.ActionConfig)
  498. return cmd.Run(name)
  499. }
  500. // RollbackRelease rolls a release back to a specified revision/version
  501. func (a *Agent) RollbackRelease(
  502. ctx context.Context,
  503. name string,
  504. version int,
  505. ) error {
  506. ctx, span := telemetry.NewSpan(ctx, "helm-rollback-release")
  507. defer span.End()
  508. telemetry.WithAttributes(span,
  509. telemetry.AttributeKV{Key: "name", Value: name},
  510. telemetry.AttributeKV{Key: "version", Value: version},
  511. )
  512. cmd := action.NewRollback(a.ActionConfig)
  513. cmd.Version = version
  514. return cmd.Run(name)
  515. }
  516. // ------------------------ Helm agent helper functions ------------------------ //
  517. // checkIfInstallable validates if a chart can be installed
  518. // Application chart type is only installable
  519. func checkIfInstallable(ch *chart.Chart) error {
  520. switch ch.Metadata.Type {
  521. case "", "application":
  522. return nil
  523. }
  524. return errors.Errorf("%s charts are not installable", ch.Metadata.Type)
  525. }