src/jetstream/plugins/kubernetes/install_release.go
package kubernetes
import (
"bytes"
"encoding/json"
"fmt"
"github.com/labstack/echo/v4"
log "github.com/sirupsen/logrus"
"sigs.k8s.io/yaml"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader"
"github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces"
)
const chartCollection = "charts"
type installRequest struct {
Endpoint string `json:"endpoint"`
MonocularEndpoint string `json:"monocularEndpoint"`
Name string `json:"releaseName"`
Namespace string `json:"releaseNamespace"`
Values string `json:"values"`
ChartURL string `json:"chartUrl"`
Chart struct {
Name string `json:"chartName"`
Repository string `json:"repo"`
Version string `json:"version"`
} `json:"chart"`
}
type upgradeRequest struct {
MonocularEndpoint string `json:"monocularEndpoint"`
Values string `json:"values"`
ChartURL string `json:"chartUrl"`
Chart struct {
Name string `json:"name"`
Repository string `json:"repo"`
Version string `json:"version"`
} `json:"chart"`
RestartPods bool `json:"restartPods"`
}
// InstallRelease will install a Helm 3 release
func (c *KubernetesSpecification) InstallRelease(ec echo.Context) error {
bodyReader := ec.Request().Body
buf := new(bytes.Buffer)
buf.ReadFrom(bodyReader)
var params installRequest
err := json.Unmarshal(buf.Bytes(), ¶ms)
if err != nil {
return interfaces.NewJetstreamUserErrorf("Could not get Create Release Parameters: %v+", err)
}
// Client must give us the download URL for the chart
if len(params.ChartURL) == 0 {
return interfaces.NewJetstreamUserError("Client did not supply Chart download URL")
}
chart, err := c.loadChart(params.ChartURL)
if err != nil {
return interfaces.NewJetstreamErrorf("Could not load chart: %v+", err)
}
endpointGUID := params.Endpoint
userGUID := ec.Get("user_id").(string)
config, hc, err := c.GetHelmConfiguration(endpointGUID, userGUID, params.Namespace)
if err != nil {
return interfaces.NewJetstreamUserErrorf("Could not get Helm Configuration for endpoint: %+v", err)
}
defer hc.Cleanup()
userSuppliedValues := map[string]interface{}{}
if err := yaml.Unmarshal([]byte(params.Values), &userSuppliedValues); err != nil {
// Could not parse the user's values
return interfaces.NewJetstreamUserErrorf("Could not parse values: %+v", err)
}
// In Helm 3, the namespace must already exist
kubeClient, _ := c.GetConfigForEndpointUser(endpointGUID, userGUID)
clientset, _ := kubernetes.NewForConfig(kubeClient)
coreclient := clientset.CoreV1()
_, err = coreclient.Namespaces().Get(params.Namespace, metav1.GetOptions{})
if err != nil {
return interfaces.NewJetstreamErrorf("Namespace '%s' does not exist", params.Namespace)
}
// Check release name is valid and does not already exist
statusAction := action.NewStatus(config)
_, err = statusAction.Run(params.Name)
if err == nil {
return interfaces.NewJetstreamUserError("A Release with that name already exists - please choose another")
}
install := action.NewInstall(config)
install.ReleaseName = params.Name
install.Namespace = params.Namespace
release, err := install.Run(chart, userSuppliedValues)
if err != nil {
return interfaces.NewJetstreamUserErrorf(fmt.Sprintf("Error installing: %+v", err))
}
return ec.JSON(200, release)
}
// Load the Helm chart for the given repository, name and version
func (c *KubernetesSpecification) loadChart(downloadURL string) (*chart.Chart, error) {
log.Debugf("Helm Chart Download URL: %s", downloadURL)
// NWM: Should we look up Helm Repository endpoint and use the value from that
httpClient := c.portalProxy.GetHttpClient(false)
resp, err := httpClient.Get(downloadURL)
if err != nil {
return nil, fmt.Errorf("Could not download Chart Archive: %s", err)
}
if resp.StatusCode != 200 {
return nil, fmt.Errorf("Could not download Chart Archive: %s", resp.Status)
}
defer resp.Body.Close()
return loader.LoadArchive(resp.Body)
}
// DeleteRelease will delete a release
func (c *KubernetesSpecification) DeleteRelease(ec echo.Context) error {
endpointGUID := ec.Param("endpoint")
releaseName := ec.Param("name")
namespace := ec.Param("namespace")
userGUID := ec.Get("user_id").(string)
config, hc, err := c.GetHelmConfiguration(endpointGUID, userGUID, namespace)
if err != nil {
return interfaces.NewJetstreamErrorf("Could not get Helm Configuration for endpoint: %+v", err)
}
defer hc.Cleanup()
uninstall := action.NewUninstall(config)
deleteResponse, err := uninstall.Run(releaseName)
if err != nil {
return interfaces.NewJetstreamError("Could not delete Helm Release")
}
return ec.JSON(200, deleteResponse)
}
// GetReleaseHistory will get the history for a release
func (c *KubernetesSpecification) GetReleaseHistory(ec echo.Context) error {
endpointGUID := ec.Param("endpoint")
releaseName := ec.Param("name")
namespace := ec.Param("namespace")
userGUID := ec.Get("user_id").(string)
config, hc, err := c.GetHelmConfiguration(endpointGUID, userGUID, namespace)
if err != nil {
return interfaces.NewJetstreamErrorf("Could not get Helm Configuration for endpoint: %+v", err)
}
defer hc.Cleanup()
history := action.NewHistory(config)
historyResponse, err := history.Run(releaseName)
if err != nil {
return interfaces.NewJetstreamError("Could not get history for the Helm Release")
}
return ec.JSON(200, historyResponse)
}
// UpgradeRelease will upgrade the specified release
func (c *KubernetesSpecification) UpgradeRelease(ec echo.Context) error {
endpointGUID := ec.Param("endpoint")
releaseName := ec.Param("name")
namespace := ec.Param("namespace")
userGUID := ec.Get("user_id").(string)
bodyReader := ec.Request().Body
buf := new(bytes.Buffer)
buf.ReadFrom(bodyReader)
var params upgradeRequest
err := json.Unmarshal(buf.Bytes(), ¶ms)
if err != nil {
return interfaces.NewJetstreamErrorf("Could not get Upgrade Release Parameters: %+v", err)
}
// Client must give us the download URL for the chart
if len(params.ChartURL) == 0 {
return interfaces.NewJetstreamErrorf("Client did not supply Chart download URL")
}
chart, err := c.loadChart(params.ChartURL)
if err != nil {
return interfaces.NewJetstreamErrorf("Could not load chart for upgrade: %+v", err)
}
config, hc, err := c.GetHelmConfiguration(endpointGUID, userGUID, namespace)
if err != nil {
return interfaces.NewJetstreamErrorf("Could not get Helm Configuration for endpoint: %+v", err)
}
defer hc.Cleanup()
userSuppliedValues := map[string]interface{}{}
if err := yaml.Unmarshal([]byte(params.Values), &userSuppliedValues); err != nil {
// Could not parse the user's values
return interfaces.NewJetstreamErrorf("Could not parse values: %+v", err)
}
upgrade := action.NewUpgrade(config)
upgradeResponse, err := upgrade.Run(releaseName, chart, userSuppliedValues)
if err != nil {
return interfaces.NewJetstreamErrorf("Could not upgrade Helm Release: %+v", err)
}
return ec.JSON(200, upgradeResponse)
}