Files
kubesphere/pkg/simple/client/openpitrix/helmwrapper/helm_wrapper.go
LiHui 83e6221f3a openpitrix crd
Signed-off-by: LiHui <andrewli@yunify.com>

delete helm repo, release and app

Signed-off-by: LiHui <andrewli@yunify.com>

Fix Dockerfile

Signed-off-by: LiHui <andrewli@yunify.com>

add unit test for category controller

Signed-off-by: LiHui <andrewli@yunify.com>

resource api

Signed-off-by: LiHui <andrewli@yunify.com>

miscellaneous

Signed-off-by: LiHui <andrewli@yunify.com>

resource api

Signed-off-by: LiHui <andrewli@yunify.com>

add s3 repo indx

Signed-off-by: LiHui <andrewli@yunify.com>

attachment api

Signed-off-by: LiHui <andrewli@yunify.com>

repo controller test

Signed-off-by: LiHui <andrewli@yunify.com>

application controller test

Signed-off-by: LiHui <andrewli@yunify.com>

release metric

Signed-off-by: LiHui <andrewli@yunify.com>

helm release controller test

Signed-off-by: LiHui <andrewli@yunify.com>

move constants to /pkg/apis/application

Signed-off-by: LiHui <andrewli@yunify.com>

remove unused code

Signed-off-by: LiHui <andrewli@yunify.com>

add license header

Signed-off-by: LiHui <andrewli@yunify.com>

Fix bugs

Signed-off-by: LiHui <andrewli@yunify.com>

cluster cluent

Signed-off-by: LiHui <andrewli@yunify.com>

format code

Signed-off-by: LiHui <andrewli@yunify.com>

move workspace,cluster from spec to labels

Signed-off-by: LiHui <andrewli@yunify.com>

add license header

Signed-off-by: LiHui <andrewli@yunify.com>

openpitrix test

Signed-off-by: LiHui <andrewli@yunify.com>

add worksapce labels for app in appstore

Signed-off-by: LiHui <andrewli@yunify.com>
2021-03-09 18:30:02 +08:00

471 lines
11 KiB
Go

/*
Copyright 2020 The KubeSphere Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package helmwrapper
import (
"bytes"
"encoding/json"
"fmt"
"k8s.io/klog"
kpath "k8s.io/utils/path"
"kubesphere.io/kubesphere/pkg/utils/idutils"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
"time"
)
var (
UninstallNotFoundFormat = "Error: uninstall: Release not loaded: %s: release: not found"
StatusNotFoundFormat = "Error: release: not found"
)
type HelmRes struct {
Message string
}
type releaseStatus struct {
Name string `json:"name,omitempty"`
Info *Info `json:"info,omitempty"`
}
// copy from helm
// Info describes release information.
type Info struct {
// FirstDeployed is when the release was first deployed.
FirstDeployed time.Time `json:"first_deployed,omitempty"`
// LastDeployed is when the release was last deployed.
LastDeployed time.Time `json:"last_deployed,omitempty"`
// Deleted tracks when this object was deleted.
Deleted time.Time `json:"deleted"`
// Description is human-friendly "log entry" about this release.
Description string `json:"description,omitempty"`
// Status is the current state of the release
Status string `json:"status,omitempty"`
// Contains the rendered templates/NOTES.txt if available
Notes string `json:"notes,omitempty"`
}
type helmRlsStatus struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
Revision int `json:"revision"`
Status string `json:"status"`
Chart string `json:"chart"`
AppVersion string `json:"app_version"`
}
var _ HelmWrapper = &helmWrapper{}
type HelmWrapper interface {
Install(chartName, chartData, values string) (HelmRes, error)
// upgrade a release
Upgrade(chartName, chartData, values string) (HelmRes, error)
Uninstall() (HelmRes, error)
// Get manifests
Manifest() (string, error)
}
func (c *helmWrapper) Status() (status *releaseStatus, err error) {
if err = c.ensureWorkspace(); err != nil {
return nil, err
}
defer c.cleanup()
stdout := &bytes.Buffer{}
stderr := &bytes.Buffer{}
cmd := exec.Cmd{
Path: c.cmdPath,
Dir: c.Workspace(),
Args: []string{
c.cmdPath,
"status",
fmt.Sprintf("%s", c.ReleaseName),
"--namespace",
c.Namespace,
"--output",
"json",
},
Stderr: stderr,
Stdout: stdout,
}
if c.kubeConfigPath() != "" {
cmd.Args = append(cmd.Args, "--kubeconfig", c.kubeConfigPath())
}
err = cmd.Run()
if err != nil {
klog.Errorf("namespace: %s, name: %s, run command failed, stderr: %s, error: %v", c.Namespace, c.ReleaseName, stderr, err)
return
} else {
klog.V(2).Infof("namespace: %s, name: %s, run command success", c.Namespace, c.ReleaseName)
klog.V(8).Infof("namespace: %s, name: %s, run command success, stdout: %s", c.Namespace, c.ReleaseName, stdout)
}
status = &releaseStatus{}
err = json.Unmarshal(stdout.Bytes(), status)
if err != nil {
klog.Errorf("namespace: %s, name: %s, json unmarshal failed, error: %s", c.Namespace, c.ReleaseName, err)
}
return
}
func (c *helmWrapper) Workspace() string {
if c.workspaceSuffix == "" {
return path.Join(c.base, fmt.Sprintf("%s_%s", c.Namespace, c.ReleaseName))
} else {
return path.Join(c.base, fmt.Sprintf("%s_%s_%s", c.Namespace, c.ReleaseName, c.workspaceSuffix))
}
}
type helmWrapper struct {
// KubeConfig string
Kubeconfig string
Namespace string
// helm release name
ReleaseName string
ChartName string
// helm cmd path
cmdPath string
// base should be /dev/shm on linux
base string
workspaceSuffix string
dryRun bool
mock bool
}
func (c *helmWrapper) kubeConfigPath() string {
if len(c.Kubeconfig) == 0 {
return ""
}
return path.Join(c.Workspace(), "kube.config")
}
func (c *helmWrapper) chartPath() string {
return filepath.Join(c.Workspace(), fmt.Sprintf("%s.tgz", c.ChartName))
}
func (c *helmWrapper) cleanup() {
if err := os.RemoveAll(c.Workspace()); err != nil {
klog.Errorf("remove dir %s faield, error: %s", c.Workspace(), err)
}
}
func (c *helmWrapper) Set(options ...Option) {
for _, option := range options {
option(c)
}
}
type Option func(*helmWrapper)
func SetDryRun(dryRun bool) Option {
return func(wrapper *helmWrapper) {
wrapper.dryRun = dryRun
}
}
func SetMock(mock bool) Option {
return func(wrapper *helmWrapper) {
wrapper.mock = mock
}
}
func NewHelmWrapper(kubeconfig, ns, rls string, options ...Option) *helmWrapper {
c := &helmWrapper{
Kubeconfig: kubeconfig,
Namespace: ns,
ReleaseName: rls,
base: workspaceBase,
cmdPath: helmPath,
workspaceSuffix: idutils.GetUuid36(""),
}
for _, option := range options {
option(c)
}
return c
}
// ensureWorkspace check whether workspace exists or not.
// If not exists, create workspace dir.
func (c *helmWrapper) ensureWorkspace() error {
if exists, err := kpath.Exists(kpath.CheckFollowSymlink, c.Workspace()); err != nil {
klog.Errorf("check dir %s failed, error: %s", c.Workspace(), err)
return err
} else if !exists {
err = os.MkdirAll(c.Workspace(), os.ModeDir|os.ModePerm)
if err != nil {
klog.Errorf("mkdir %s failed, error: %s", c.Workspace(), err)
return err
}
}
if len(c.Kubeconfig) > 0 {
kubeFile, err := os.OpenFile(c.kubeConfigPath(), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return err
}
_, err = kubeFile.WriteString(c.Kubeconfig)
if err != nil {
return err
}
kubeFile.Close()
}
return nil
}
// create chart dir in workspace
// write values.yaml into workspace
func (c *helmWrapper) createChart(chartName, chartData, values string) error {
c.ChartName = chartName
// write chart
f, err := os.Create(path.Join(c.chartPath()))
if err != nil {
return err
}
_, err = f.Write([]byte(chartData))
if err != nil {
return err
}
f.Close()
// write values
f, err = os.Create(path.Join(c.Workspace(), "values.yaml"))
if err != nil {
return err
}
_, err = f.WriteString(values)
if err != nil {
return err
}
f.Close()
return nil
}
func (c *helmWrapper) Uninstall() (res HelmRes, err error) {
start := time.Now()
defer func() {
klog.V(2).Infof("run command end, namespace: %s, name: %s elapsed: %v", c.Namespace, c.ReleaseName, time.Now().Sub(start))
}()
if err = c.ensureWorkspace(); err != nil {
return
}
defer c.cleanup()
stderr := &bytes.Buffer{}
stdout := &bytes.Buffer{}
cmd := exec.Cmd{
Path: c.cmdPath,
Dir: c.Workspace(),
Stdout: stdout,
Stderr: stderr,
}
cmd.Args = make([]string, 0, 10)
// only for mock
if c.mock {
cmd.Path = os.Args[0]
cmd.Args = []string{os.Args[0], "-test.run=TestHelperProcess", "--"}
cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"}
}
cmd.Args = append(cmd.Args, c.cmdPath,
"uninstall",
c.ReleaseName,
"--namespace",
c.Namespace)
if c.dryRun {
cmd.Args = append(cmd.Args, "--dry-run")
}
if c.kubeConfigPath() != "" {
cmd.Args = append(cmd.Args, "--kubeconfig", c.kubeConfigPath())
}
klog.V(4).Infof("run command: %s", cmd.String())
err = cmd.Run()
if err != nil {
eMsg := strings.TrimSpace(stderr.String())
if fmt.Sprintf(UninstallNotFoundFormat, c.ReleaseName) == eMsg {
return res, nil
}
klog.Errorf("run command failed, stderr: %s, error: %v", eMsg, err)
res.Message = eMsg
} else {
klog.V(2).Infof("namespace: %s, name: %s, run command success", c.Namespace, c.ReleaseName)
klog.V(8).Infof("namespace: %s, name: %s, run command success, stdout: %s", c.Namespace, c.ReleaseName, stdout)
}
return
}
func (c *helmWrapper) Upgrade(chartName, chartData, values string) (res HelmRes, err error) {
// TODO: check release status first
if true {
return c.install(chartName, chartData, values, true)
} else {
klog.V(3).Infof("release %s/%s not exists, cannot upgrade it, install a new one", c.Namespace, c.ReleaseName)
return
}
}
func (c *helmWrapper) Install(chartName, chartData, values string) (res HelmRes, err error) {
return c.install(chartName, chartData, values, false)
}
func (c *helmWrapper) install(chartName, chartData, values string, upgrade bool) (res HelmRes, err error) {
if klog.V(2) {
start := time.Now()
defer func() {
klog.V(2).Infof("run command end, namespace: %s, name: %s elapsed: %v", c.Namespace, c.ReleaseName, time.Now().Sub(start))
}()
}
if err = c.ensureWorkspace(); err != nil {
return
}
defer c.cleanup()
if err = c.createChart(chartName, chartData, values); err != nil {
return
}
klog.V(8).Infof("namespace: %s, name: %s, chart values: %s", c.Namespace, c.ReleaseName, values)
stdout := &bytes.Buffer{}
stderr := &bytes.Buffer{}
cmd := exec.Cmd{
Path: c.cmdPath,
Dir: c.Workspace(),
Stdout: stdout,
Stderr: stderr,
}
cmd.Args = make([]string, 0, 10)
// only for mock
if c.mock {
cmd.Path = os.Args[0]
cmd.Args = []string{os.Args[0], "-test.run=TestHelperProcess", "--"}
cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"}
}
cmd.Args = append(cmd.Args, c.cmdPath)
if upgrade {
cmd.Args = append(cmd.Args, "upgrade")
} else {
cmd.Args = append(cmd.Args, "install")
}
cmd.Args = append(cmd.Args, c.ReleaseName, c.chartPath(), "--namespace", c.Namespace)
if len(values) > 0 {
cmd.Args = append(cmd.Args, "--values", path.Join(c.Workspace(), "values.yaml"))
}
if c.dryRun {
cmd.Args = append(cmd.Args, "--dry-run")
}
if c.kubeConfigPath() != "" {
cmd.Args = append(cmd.Args, "--kubeconfig", c.kubeConfigPath())
}
if klog.V(8) {
// output debug info
cmd.Args = append(cmd.Args, "--debug")
}
klog.V(4).Infof("run command: %s", cmd.String())
err = cmd.Run()
if err != nil {
klog.Errorf("namespace: %s, name: %s, run command: %s failed, stderr: %s, error: %v", c.Namespace, c.ReleaseName, cmd.String(), stderr, err)
res.Message = stderr.String()
} else {
klog.V(2).Infof("namespace: %s, name: %s, run command success", c.Namespace, c.ReleaseName)
klog.V(8).Infof("namespace: %s, name: %s, run command success, stdout: %s", c.Namespace, c.ReleaseName, stdout)
}
return
}
func (c *helmWrapper) Manifest() (manifest string, err error) {
if err = c.ensureWorkspace(); err != nil {
return "", err
}
defer c.cleanup()
stdout := &bytes.Buffer{}
stderr := &bytes.Buffer{}
cmd := exec.Cmd{
Path: c.cmdPath,
Dir: c.Workspace(),
Args: []string{
c.cmdPath,
"get",
"manifest",
c.ReleaseName,
"--namespace",
c.Namespace,
},
Stderr: stderr,
Stdout: stdout,
}
if c.kubeConfigPath() != "" {
cmd.Args = append(cmd.Args, "--kubeconfig", c.kubeConfigPath())
}
if klog.V(8) {
// output debug info
cmd.Args = append(cmd.Args, "--debug")
}
klog.V(4).Infof("run command: %s", cmd.String())
err = cmd.Run()
if err != nil {
klog.Errorf("namespace: %s, name: %s, run command failed, stderr: %s, error: %v", c.Namespace, c.ReleaseName, stderr, err)
return "", err
} else {
klog.V(2).Infof("namespace: %s, name: %s, run command success", c.Namespace, c.ReleaseName)
klog.V(8).Infof("namespace: %s, name: %s, run command success, stdout: %s", c.Namespace, c.ReleaseName, stdout)
}
return stdout.String(), nil
}