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>
This commit is contained in:
470
pkg/simple/client/openpitrix/helmwrapper/helm_wrapper.go
Normal file
470
pkg/simple/client/openpitrix/helmwrapper/helm_wrapper.go
Normal file
@@ -0,0 +1,470 @@
|
||||
/*
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
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
|
||||
|
||||
const (
|
||||
workspaceBase = "/tmp/helm-operator"
|
||||
helmPath = "/usr/local/bin/helm"
|
||||
)
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
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
|
||||
|
||||
const (
|
||||
workspaceBase = "/dev/shm/helm-operator"
|
||||
helmPath = "/usr/bin/helm"
|
||||
)
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
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 (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHelmInstall(t *testing.T) {
|
||||
wr := NewHelmWrapper("", "dummy", "dummy", SetMock(true))
|
||||
|
||||
res, err := wr.Install("dummy-chart", "", "dummy-value")
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
_ = res
|
||||
}
|
||||
|
||||
func TestHelperProcess(t *testing.T) {
|
||||
if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
|
||||
return
|
||||
}
|
||||
|
||||
// some code here to check arguments perhaps?
|
||||
fmt.Fprintf(os.Stdout, "helm mock success")
|
||||
os.Exit(0)
|
||||
}
|
||||
Reference in New Issue
Block a user