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:
36
pkg/simple/client/openpitrix/helmrepoindex/interface.go
Normal file
36
pkg/simple/client/openpitrix/helmrepoindex/interface.go
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
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 helmrepoindex
|
||||
|
||||
import "time"
|
||||
|
||||
type VersionInterface interface {
|
||||
GetName() string
|
||||
GetVersion() string
|
||||
GetAppVersion() string
|
||||
GetDescription() string
|
||||
GetUrls() string
|
||||
GetVersionName() string
|
||||
GetIcon() string
|
||||
GetHome() string
|
||||
GetSources() string
|
||||
GetKeywords() string
|
||||
GetMaintainers() string
|
||||
GetScreenshots() string
|
||||
GetPackageName() string
|
||||
GetCreateTime() time.Time
|
||||
}
|
||||
104
pkg/simple/client/openpitrix/helmrepoindex/load_chart.go
Normal file
104
pkg/simple/client/openpitrix/helmrepoindex/load_chart.go
Normal file
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
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 helmrepoindex
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"helm.sh/helm/v3/pkg/getter"
|
||||
"kubesphere.io/kubesphere/pkg/apis/application/v1alpha1"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/s3"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func parseS3Url(parse *url.URL) (region, endpoint, bucket, path string) {
|
||||
if strings.HasPrefix(parse.Host, "s3.") {
|
||||
region = strings.Split(parse.Host, ".")[1]
|
||||
endpoint = fmt.Sprintf("https://%s", parse.Host)
|
||||
} else {
|
||||
region = "us-east-1"
|
||||
endpoint = fmt.Sprintf("http://%s", parse.Host)
|
||||
}
|
||||
parts := strings.Split(strings.TrimPrefix(parse.Path, "/"), "/")
|
||||
if len(parts) > 0 {
|
||||
bucket = parts[0]
|
||||
path = strings.Join(parts[1:], "/")
|
||||
} else {
|
||||
bucket = parse.Path
|
||||
}
|
||||
|
||||
return region, endpoint, bucket, path
|
||||
}
|
||||
|
||||
func loadData(ctx context.Context, u string, cred *v1alpha1.HelmRepoCredential) (*bytes.Buffer, error) {
|
||||
parsedURL, err := url.Parse(u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var resp *bytes.Buffer
|
||||
if strings.HasPrefix(u, "s3://") {
|
||||
region, endpoint, bucket, p := parseS3Url(parsedURL)
|
||||
client, err := s3.NewS3Client(&s3.Options{
|
||||
Endpoint: endpoint,
|
||||
Bucket: bucket,
|
||||
Region: region,
|
||||
AccessKeyID: cred.AccessKeyID,
|
||||
SecretAccessKey: cred.SecretAccessKey,
|
||||
DisableSSL: !strings.HasPrefix(region, "https://"),
|
||||
ForcePathStyle: true,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := client.Read(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp = bytes.NewBuffer(data)
|
||||
} else {
|
||||
skipTLS := true
|
||||
if cred.InsecureSkipTLSVerify != nil && !*cred.InsecureSkipTLSVerify {
|
||||
skipTLS = false
|
||||
}
|
||||
|
||||
indexURL := parsedURL.String()
|
||||
// TODO add user-agent
|
||||
g, _ := getter.NewHTTPGetter()
|
||||
resp, err = g.Get(indexURL,
|
||||
getter.WithTimeout(5*time.Minute),
|
||||
getter.WithURL(u),
|
||||
getter.WithInsecureSkipVerifyTLS(skipTLS),
|
||||
getter.WithTLSClientConfig(cred.CertFile, cred.KeyFile, cred.CAFile),
|
||||
getter.WithBasicAuth(cred.Username, cred.Password),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func LoadChart(ctx context.Context, u string, cred *v1alpha1.HelmRepoCredential) (*bytes.Buffer, error) {
|
||||
return loadData(ctx, u, cred)
|
||||
}
|
||||
96
pkg/simple/client/openpitrix/helmrepoindex/load_package.go
Normal file
96
pkg/simple/client/openpitrix/helmrepoindex/load_package.go
Normal file
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
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 helmrepoindex
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"helm.sh/helm/v3/pkg/repo"
|
||||
"k8s.io/klog"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"helm.sh/helm/v3/pkg/chart/loader"
|
||||
)
|
||||
|
||||
func LoadPackage(pkg []byte) (VersionInterface, error) {
|
||||
p, err := loader.LoadArchive(bytes.NewReader(pkg))
|
||||
if err != nil {
|
||||
klog.Errorf("Failed to load package, error: %+v", err)
|
||||
return nil, err
|
||||
}
|
||||
return HelmVersionWrapper{ChartVersion: &repo.ChartVersion{Metadata: p.Metadata}}, nil
|
||||
}
|
||||
|
||||
type HelmVersionWrapper struct {
|
||||
*repo.ChartVersion
|
||||
}
|
||||
|
||||
func (h HelmVersionWrapper) GetIcon() string { return h.ChartVersion.Icon }
|
||||
func (h HelmVersionWrapper) GetName() string { return h.ChartVersion.Name }
|
||||
func (h HelmVersionWrapper) GetHome() string { return h.ChartVersion.Home }
|
||||
func (h HelmVersionWrapper) GetVersion() string { return h.ChartVersion.Version }
|
||||
func (h HelmVersionWrapper) GetAppVersion() string { return h.ChartVersion.AppVersion }
|
||||
func (h HelmVersionWrapper) GetDescription() string { return h.ChartVersion.Description }
|
||||
func (h HelmVersionWrapper) GetCreateTime() time.Time { return h.ChartVersion.Created }
|
||||
func (h HelmVersionWrapper) GetUrls() string {
|
||||
if len(h.ChartVersion.URLs) == 0 {
|
||||
return ""
|
||||
}
|
||||
return h.ChartVersion.URLs[0]
|
||||
}
|
||||
|
||||
func (h HelmVersionWrapper) GetSources() string {
|
||||
if len(h.ChartVersion.Sources) == 0 {
|
||||
return ""
|
||||
}
|
||||
s, _ := json.Marshal(h.ChartVersion.Sources)
|
||||
return string(s)
|
||||
}
|
||||
|
||||
func (h HelmVersionWrapper) GetKeywords() string {
|
||||
return strings.Join(h.ChartVersion.Keywords, ",")
|
||||
}
|
||||
|
||||
func (h HelmVersionWrapper) GetMaintainers() string {
|
||||
if len(h.ChartVersion.Maintainers) == 0 {
|
||||
return ""
|
||||
}
|
||||
s, _ := json.Marshal(h.ChartVersion.Maintainers)
|
||||
return string(s)
|
||||
}
|
||||
|
||||
func (h HelmVersionWrapper) GetScreenshots() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (h HelmVersionWrapper) GetVersionName() string {
|
||||
versionName := h.GetVersion()
|
||||
if h.GetAppVersion() != "" {
|
||||
versionName += fmt.Sprintf(" [%s]", h.GetAppVersion())
|
||||
}
|
||||
return versionName
|
||||
}
|
||||
|
||||
func (h HelmVersionWrapper) GetPackageName() string {
|
||||
file := h.GetUrls()
|
||||
if len(file) == 0 {
|
||||
return fmt.Sprintf("%s-%s.tgz", h.Name, h.Version)
|
||||
}
|
||||
return file
|
||||
}
|
||||
270
pkg/simple/client/openpitrix/helmrepoindex/repo_index.go
Normal file
270
pkg/simple/client/openpitrix/helmrepoindex/repo_index.go
Normal file
@@ -0,0 +1,270 @@
|
||||
/*
|
||||
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 helmrepoindex
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/zlib"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
helmrepo "helm.sh/helm/v3/pkg/repo"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"kubesphere.io/kubesphere/pkg/apis/application/v1alpha1"
|
||||
"kubesphere.io/kubesphere/pkg/constants"
|
||||
"kubesphere.io/kubesphere/pkg/utils/idutils"
|
||||
"sigs.k8s.io/yaml"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const IndexYaml = "index.yaml"
|
||||
|
||||
func LoadRepoIndex(ctx context.Context, u string, cred *v1alpha1.HelmRepoCredential) (*helmrepo.IndexFile, error) {
|
||||
|
||||
if !strings.HasSuffix(u, "/") {
|
||||
u = fmt.Sprintf("%s/%s", u, IndexYaml)
|
||||
} else {
|
||||
u = fmt.Sprintf("%s%s", u, IndexYaml)
|
||||
}
|
||||
|
||||
resp, err := loadData(ctx, u, cred)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
indexFile, err := loadIndex(resp.Bytes())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return indexFile, nil
|
||||
}
|
||||
|
||||
// loadIndex loads an index file and does minimal validity checking.
|
||||
//
|
||||
// This will fail if API Version is not set (ErrNoAPIVersion) or if the unmarshal fails.
|
||||
func loadIndex(data []byte) (*helmrepo.IndexFile, error) {
|
||||
i := &helmrepo.IndexFile{}
|
||||
if err := yaml.UnmarshalStrict(data, i); err != nil {
|
||||
return i, err
|
||||
}
|
||||
i.SortEntries()
|
||||
if i.APIVersion == "" {
|
||||
return i, helmrepo.ErrNoAPIVersion
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// merge new index with index from crd
|
||||
func MergeRepoIndex(index *helmrepo.IndexFile, existsSavedIndex *SavedIndex) *SavedIndex {
|
||||
saved := &SavedIndex{}
|
||||
if index == nil {
|
||||
return existsSavedIndex
|
||||
}
|
||||
|
||||
saved.Applications = make(map[string]*Application)
|
||||
if existsSavedIndex != nil {
|
||||
for name := range existsSavedIndex.Applications {
|
||||
saved.Applications[name] = existsSavedIndex.Applications[name]
|
||||
}
|
||||
}
|
||||
|
||||
// just copy fields from index
|
||||
saved.APIVersion = index.APIVersion
|
||||
saved.Generated = index.Generated
|
||||
saved.PublicKeys = index.PublicKeys
|
||||
|
||||
allNames := make(map[string]bool, len(index.Entries))
|
||||
for name, versions := range index.Entries {
|
||||
// add new applications
|
||||
if application, exists := saved.Applications[name]; !exists {
|
||||
application = &Application{
|
||||
Name: name,
|
||||
ApplicationId: idutils.GetUuid36(v1alpha1.HelmApplicationIdPrefix),
|
||||
Description: versions[0].Description,
|
||||
Icon: versions[0].Icon,
|
||||
}
|
||||
|
||||
charts := make([]*ChartVersion, 0, len(versions))
|
||||
for ind := range versions {
|
||||
chart := &ChartVersion{
|
||||
ApplicationId: application.ApplicationId,
|
||||
ApplicationVersionId: idutils.GetUuid36(v1alpha1.HelmApplicationVersionIdPrefix),
|
||||
ChartVersion: *versions[ind],
|
||||
}
|
||||
charts = append(charts, chart)
|
||||
}
|
||||
application.Charts = charts
|
||||
saved.Applications[name] = application
|
||||
} else {
|
||||
// update exists applications
|
||||
savedChartVersion := make(map[string]struct{})
|
||||
for _, ver := range application.Charts {
|
||||
savedChartVersion[ver.Version] = struct{}{}
|
||||
}
|
||||
charts := application.Charts
|
||||
for _, ver := range versions {
|
||||
// add new chart version
|
||||
if _, exists := savedChartVersion[ver.Version]; !exists {
|
||||
chart := &ChartVersion{
|
||||
ApplicationId: application.ApplicationId,
|
||||
ApplicationVersionId: idutils.GetUuid36(v1alpha1.HelmApplicationVersionIdPrefix),
|
||||
ChartVersion: *ver,
|
||||
}
|
||||
charts = append(charts, chart)
|
||||
}
|
||||
application.Charts = charts
|
||||
saved.Applications[name] = application
|
||||
}
|
||||
}
|
||||
allNames[name] = true
|
||||
}
|
||||
|
||||
for name := range saved.Applications {
|
||||
if _, exists := allNames[name]; !exists {
|
||||
delete(saved.Applications, name)
|
||||
}
|
||||
}
|
||||
|
||||
return saved
|
||||
}
|
||||
|
||||
func (i *SavedIndex) GetApplicationVersion(appId, versionId string) *v1alpha1.HelmApplicationVersion {
|
||||
for _, app := range i.Applications {
|
||||
if app.ApplicationId == appId {
|
||||
for _, ver := range app.Charts {
|
||||
if ver.ApplicationVersionId == versionId {
|
||||
version := &v1alpha1.HelmApplicationVersion{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: versionId,
|
||||
Labels: map[string]string{
|
||||
constants.ChartApplicationIdLabelKey: appId,
|
||||
},
|
||||
},
|
||||
Spec: v1alpha1.HelmApplicationVersionSpec{
|
||||
URLs: ver.URLs,
|
||||
Digest: ver.Digest,
|
||||
Metadata: &v1alpha1.Metadata{
|
||||
Name: ver.Name,
|
||||
AppVersion: ver.AppVersion,
|
||||
Version: ver.Version,
|
||||
Annotations: ver.Annotations,
|
||||
},
|
||||
},
|
||||
}
|
||||
return version
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type SavedIndex struct {
|
||||
APIVersion string `json:"apiVersion"`
|
||||
Generated time.Time `json:"generated"`
|
||||
Applications map[string]*Application `json:"apps"`
|
||||
PublicKeys []string `json:"publicKeys,omitempty"`
|
||||
|
||||
// Annotations are additional mappings uninterpreted by Helm. They are made available for
|
||||
// other applications to add information to the index file.
|
||||
Annotations map[string]string `json:"annotations,omitempty"`
|
||||
}
|
||||
|
||||
func ByteArrayToSavedIndex(data []byte) (*SavedIndex, error) {
|
||||
ret := &SavedIndex{}
|
||||
if len(data) == 0 {
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
enc := base64.URLEncoding
|
||||
buf := make([]byte, enc.DecodedLen(len(data)))
|
||||
n, err := enc.Decode(buf, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = buf[:n]
|
||||
|
||||
r, err := zlib.NewReader(bytes.NewBuffer(buf))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.Close()
|
||||
b, err := ioutil.ReadAll(r)
|
||||
|
||||
if err != nil && err != io.EOF {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(b, ret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (i *SavedIndex) Bytes() ([]byte, error) {
|
||||
|
||||
d, err := json.Marshal(i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
w := zlib.NewWriter(buf)
|
||||
_, err = w.Write(d)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = w.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encSrc := buf.Bytes()
|
||||
|
||||
enc := base64.URLEncoding
|
||||
ret := make([]byte, enc.EncodedLen(len(encSrc)))
|
||||
|
||||
enc.Encode(ret, encSrc)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// chart version with app id and app version id
|
||||
type ChartVersion struct {
|
||||
// Do not save ApplicationId into crd
|
||||
ApplicationId string `json:"-"`
|
||||
ApplicationVersionId string `json:"verId"`
|
||||
helmrepo.ChartVersion `json:",inline"`
|
||||
}
|
||||
|
||||
type Application struct {
|
||||
// application name
|
||||
Name string `json:"name"`
|
||||
ApplicationId string `json:"appId"`
|
||||
// chart description
|
||||
Description string `json:"desc"`
|
||||
// application status
|
||||
Status string `json:"status"`
|
||||
// The URL to an icon file.
|
||||
Icon string `json:"icon,omitempty"`
|
||||
|
||||
Charts []*ChartVersion `json:"charts"`
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
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 helmrepoindex
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"kubesphere.io/kubesphere/pkg/apis/application/v1alpha1"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLoadRepo(t *testing.T) {
|
||||
|
||||
u := "https://charts.kubesphere.io/main"
|
||||
|
||||
index, err := LoadRepoIndex(context.TODO(), u, &v1alpha1.HelmRepoCredential{})
|
||||
if err != nil {
|
||||
t.Errorf("load repo failed, err: %s", err)
|
||||
t.Failed()
|
||||
return
|
||||
}
|
||||
|
||||
for _, entry := range index.Entries {
|
||||
chartUrl := entry[0].URLs[0]
|
||||
|
||||
if !(strings.HasPrefix(chartUrl, "https://") || strings.HasPrefix(chartUrl, "http://")) {
|
||||
chartUrl = fmt.Sprintf("%s/%s", u, chartUrl)
|
||||
}
|
||||
chartData, err := LoadChart(context.TODO(), chartUrl, &v1alpha1.HelmRepoCredential{})
|
||||
if err != nil {
|
||||
t.Errorf("load chart data failed, err: %s", err)
|
||||
t.Failed()
|
||||
}
|
||||
_ = chartData
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
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)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,326 +0,0 @@
|
||||
/*
|
||||
|
||||
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 openpitrix
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/golang/protobuf/ptypes/wrappers"
|
||||
"k8s.io/klog"
|
||||
"openpitrix.io/openpitrix/pkg/manager"
|
||||
"openpitrix.io/openpitrix/pkg/pb"
|
||||
"openpitrix.io/openpitrix/pkg/sender"
|
||||
"openpitrix.io/openpitrix/pkg/util/ctxutil"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
RuntimeAnnotationKey = "openpitrix_runtime"
|
||||
KubernetesProvider = "kubernetes"
|
||||
Unknown = "-"
|
||||
DeploySuffix = "-Deployment"
|
||||
DaemonSuffix = "-DaemonSet"
|
||||
StateSuffix = "-StatefulSet"
|
||||
SystemUsername = "system"
|
||||
SystemUserPath = ":system"
|
||||
)
|
||||
|
||||
type Client interface {
|
||||
pb.RuntimeManagerClient
|
||||
pb.ClusterManagerClient
|
||||
pb.AppManagerClient
|
||||
pb.RepoManagerClient
|
||||
pb.CategoryManagerClient
|
||||
pb.AttachmentManagerClient
|
||||
pb.RepoIndexerClient
|
||||
// upsert the openpitrix runtime when cluster is updated or created
|
||||
UpsertRuntime(cluster string, kubeConfig string) error
|
||||
// clean up the openpitrix runtime when cluster is deleted
|
||||
CleanupRuntime(cluster string) error
|
||||
// migrate the openpitrix runtime when upgrade ks2.x to ks3.x
|
||||
MigrateRuntime(runtimeId string, cluster string) error
|
||||
}
|
||||
|
||||
type client struct {
|
||||
pb.RuntimeManagerClient
|
||||
pb.ClusterManagerClient
|
||||
pb.AppManagerClient
|
||||
pb.RepoManagerClient
|
||||
pb.CategoryManagerClient
|
||||
pb.AttachmentManagerClient
|
||||
pb.RepoIndexerClient
|
||||
}
|
||||
|
||||
func (c *client) UpsertRuntime(cluster string, kubeConfig string) error {
|
||||
ctx := SystemContext()
|
||||
req := &pb.CreateRuntimeCredentialRequest{
|
||||
Name: &wrappers.StringValue{Value: fmt.Sprintf("kubeconfig-%s", cluster)},
|
||||
Provider: &wrappers.StringValue{Value: KubernetesProvider},
|
||||
Description: &wrappers.StringValue{Value: "kubeconfig"},
|
||||
RuntimeUrl: &wrappers.StringValue{Value: "kubesphere"},
|
||||
RuntimeCredentialContent: &wrappers.StringValue{Value: kubeConfig},
|
||||
RuntimeCredentialId: &wrappers.StringValue{Value: cluster},
|
||||
}
|
||||
_, err := c.CreateRuntimeCredential(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = c.CreateRuntime(ctx, &pb.CreateRuntimeRequest{
|
||||
Name: &wrappers.StringValue{Value: cluster},
|
||||
RuntimeCredentialId: &wrappers.StringValue{Value: cluster},
|
||||
Provider: &wrappers.StringValue{Value: KubernetesProvider},
|
||||
Zone: &wrappers.StringValue{Value: cluster},
|
||||
RuntimeId: &wrappers.StringValue{Value: cluster},
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *client) CleanupRuntime(cluster string) error {
|
||||
ctx := SystemContext()
|
||||
_, err := c.DeleteClusterInRuntime(ctx, &pb.DeleteClusterInRuntimeRequest{
|
||||
RuntimeId: []string{cluster},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *client) MigrateRuntime(runtimeId string, cluster string) error {
|
||||
ctx := SystemContext()
|
||||
_, err := c.MigrateClusterInRuntime(ctx, &pb.MigrateClusterInRuntimeRequest{
|
||||
FromRuntimeId: runtimeId,
|
||||
ToRuntimeId: cluster,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func parseToHostPort(endpoint string) (string, int, error) {
|
||||
args := strings.Split(endpoint, ":")
|
||||
if len(args) != 2 {
|
||||
return "", 0, fmt.Errorf("invalid server host: %s", endpoint)
|
||||
}
|
||||
host := args[0]
|
||||
port, err := strconv.Atoi(args[1])
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
return host, port, nil
|
||||
}
|
||||
|
||||
func newRuntimeManagerClient(endpoint string) (pb.RuntimeManagerClient, error) {
|
||||
if len(endpoint) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
host, port, err := parseToHostPort(endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conn, err := manager.NewClient(host, port)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pb.NewRuntimeManagerClient(conn), nil
|
||||
}
|
||||
func newClusterManagerClient(endpoint string) (pb.ClusterManagerClient, error) {
|
||||
if len(endpoint) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
host, port, err := parseToHostPort(endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn, err := manager.NewClient(host, port)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pb.NewClusterManagerClient(conn), nil
|
||||
}
|
||||
func newCategoryManagerClient(endpoint string) (pb.CategoryManagerClient, error) {
|
||||
if len(endpoint) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
host, port, err := parseToHostPort(endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn, err := manager.NewClient(host, port)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pb.NewCategoryManagerClient(conn), nil
|
||||
}
|
||||
|
||||
func newAttachmentManagerClient(endpoint string) (pb.AttachmentManagerClient, error) {
|
||||
if len(endpoint) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
host, port, err := parseToHostPort(endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn, err := manager.NewClient(host, port)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pb.NewAttachmentManagerClient(conn), nil
|
||||
}
|
||||
|
||||
func newRepoManagerClient(endpoint string) (pb.RepoManagerClient, error) {
|
||||
if len(endpoint) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
host, port, err := parseToHostPort(endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn, err := manager.NewClient(host, port)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pb.NewRepoManagerClient(conn), nil
|
||||
}
|
||||
|
||||
func newRepoIndexer(endpoint string) (pb.RepoIndexerClient, error) {
|
||||
if len(endpoint) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
host, port, err := parseToHostPort(endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn, err := manager.NewClient(host, port)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pb.NewRepoIndexerClient(conn), nil
|
||||
}
|
||||
|
||||
func newAppManagerClient(endpoint string) (pb.AppManagerClient, error) {
|
||||
if len(endpoint) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
host, port, err := parseToHostPort(endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn, err := manager.NewClient(host, port)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pb.NewAppManagerClient(conn), nil
|
||||
}
|
||||
|
||||
// will return a nil client and nil error if endpoint is empty
|
||||
func NewClient(options *Options) (Client, error) {
|
||||
if options.IsEmpty() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
runtimeMangerClient, err := newRuntimeManagerClient(options.RuntimeManagerEndpoint)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
clusterManagerClient, err := newClusterManagerClient(options.ClusterManagerEndpoint)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
repoManagerClient, err := newRepoManagerClient(options.RepoManagerEndpoint)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
repoIndexerClient, err := newRepoIndexer(options.RepoIndexerEndpoint)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
appManagerClient, err := newAppManagerClient(options.AppManagerEndpoint)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
categoryManagerClient, err := newCategoryManagerClient(options.CategoryManagerEndpoint)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
attachmentManagerClient, err := newAttachmentManagerClient(options.AttachmentManagerEndpoint)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client := client{
|
||||
RuntimeManagerClient: runtimeMangerClient,
|
||||
ClusterManagerClient: clusterManagerClient,
|
||||
RepoManagerClient: repoManagerClient,
|
||||
AppManagerClient: appManagerClient,
|
||||
CategoryManagerClient: categoryManagerClient,
|
||||
AttachmentManagerClient: attachmentManagerClient,
|
||||
RepoIndexerClient: repoIndexerClient,
|
||||
}
|
||||
|
||||
return &client, nil
|
||||
}
|
||||
|
||||
func SystemContext() context.Context {
|
||||
ctx := context.Background()
|
||||
ctx = ctxutil.ContextWithSender(ctx, sender.New(SystemUsername, SystemUserPath, ""))
|
||||
return ctx
|
||||
}
|
||||
func ContextWithUsername(username string) context.Context {
|
||||
ctx := context.Background()
|
||||
if username == "" {
|
||||
username = SystemUsername
|
||||
}
|
||||
ctx = ctxutil.ContextWithSender(ctx, sender.New(username, SystemUserPath, ""))
|
||||
return ctx
|
||||
}
|
||||
|
||||
func IsNotFound(err error) bool {
|
||||
if strings.Contains(err.Error(), "not exist") {
|
||||
return true
|
||||
}
|
||||
if strings.Contains(err.Error(), "not found") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func IsDeleted(err error) bool {
|
||||
if strings.Contains(err.Error(), "is [deleted]") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -17,113 +17,58 @@ limitations under the License.
|
||||
package openpitrix
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/spf13/pflag"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/s3"
|
||||
"kubesphere.io/kubesphere/pkg/utils/reflectutils"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
RuntimeManagerEndpoint string `json:"runtimeManagerEndpoint,omitempty" yaml:"runtimeManagerEndpoint,omitempty"`
|
||||
ClusterManagerEndpoint string `json:"clusterManagerEndpoint,omitempty" yaml:"clusterManagerEndpoint,omitempty"`
|
||||
RepoManagerEndpoint string `json:"repoManagerEndpoint,omitempty" yaml:"repoManagerEndpoint,omitempty"`
|
||||
AppManagerEndpoint string `json:"appManagerEndpoint,omitempty" yaml:"appManagerEndpoint,omitempty"`
|
||||
CategoryManagerEndpoint string `json:"categoryManagerEndpoint,omitempty" yaml:"categoryManagerEndpoint,omitempty"`
|
||||
AttachmentManagerEndpoint string `json:"attachmentManagerEndpoint,omitempty" yaml:"attachmentManagerEndpoint,omitempty"`
|
||||
RepoIndexerEndpoint string `json:"repoIndexerEndpoint,omitempty" yaml:"repoIndexerEndpoint,omitempty"`
|
||||
S3Options *s3.Options `json:"s3,omitempty" yaml:"s3,omitempty" mapstructure:"s3"`
|
||||
}
|
||||
|
||||
func NewOptions() *Options {
|
||||
return &Options{}
|
||||
return &Options{
|
||||
S3Options: &s3.Options{},
|
||||
}
|
||||
}
|
||||
|
||||
// Validate check options values
|
||||
func (s *Options) Validate() []error {
|
||||
var errors []error
|
||||
|
||||
return errors
|
||||
}
|
||||
|
||||
func (s *Options) IsEmpty() bool {
|
||||
return s.S3Options == nil || s.S3Options.Endpoint == ""
|
||||
}
|
||||
|
||||
// ApplyTo overrides options if it's valid, which endpoint is not empty
|
||||
func (s *Options) ApplyTo(options *Options) {
|
||||
if options == nil {
|
||||
options = s
|
||||
return
|
||||
}
|
||||
if s.RuntimeManagerEndpoint != "" {
|
||||
if s.S3Options != nil {
|
||||
reflectutils.Override(options, s)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Options) IsEmpty() bool {
|
||||
return s.RuntimeManagerEndpoint == "" &&
|
||||
s.ClusterManagerEndpoint == "" &&
|
||||
s.RepoManagerEndpoint == "" &&
|
||||
s.AppManagerEndpoint == "" &&
|
||||
s.CategoryManagerEndpoint == "" &&
|
||||
s.AttachmentManagerEndpoint == "" &&
|
||||
s.RepoIndexerEndpoint == ""
|
||||
}
|
||||
|
||||
func (s *Options) Validate() []error {
|
||||
var errs []error
|
||||
|
||||
if s.RuntimeManagerEndpoint != "" {
|
||||
_, _, err := parseToHostPort(s.RuntimeManagerEndpoint)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("invalid host port:%s", s.RuntimeManagerEndpoint))
|
||||
}
|
||||
}
|
||||
if s.ClusterManagerEndpoint != "" {
|
||||
_, _, err := parseToHostPort(s.ClusterManagerEndpoint)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("invalid host port:%s", s.ClusterManagerEndpoint))
|
||||
}
|
||||
}
|
||||
if s.RepoManagerEndpoint != "" {
|
||||
_, _, err := parseToHostPort(s.RepoManagerEndpoint)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("invalid host port:%s", s.RepoManagerEndpoint))
|
||||
}
|
||||
}
|
||||
if s.RepoIndexerEndpoint != "" {
|
||||
_, _, err := parseToHostPort(s.RepoIndexerEndpoint)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("invalid host port:%s", s.RepoIndexerEndpoint))
|
||||
}
|
||||
}
|
||||
if s.AppManagerEndpoint != "" {
|
||||
_, _, err := parseToHostPort(s.AppManagerEndpoint)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("invalid host port:%s", s.AppManagerEndpoint))
|
||||
}
|
||||
}
|
||||
if s.CategoryManagerEndpoint != "" {
|
||||
_, _, err := parseToHostPort(s.CategoryManagerEndpoint)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("invalid host port:%s", s.CategoryManagerEndpoint))
|
||||
}
|
||||
}
|
||||
if s.AttachmentManagerEndpoint != "" {
|
||||
_, _, err := parseToHostPort(s.CategoryManagerEndpoint)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("invalid host port:%s", s.CategoryManagerEndpoint))
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
// AddFlags add options flags to command line flags,
|
||||
func (s *Options) AddFlags(fs *pflag.FlagSet, c *Options) {
|
||||
fs.StringVar(&s.RuntimeManagerEndpoint, "openpitrix-runtime-manager-endpoint", c.RuntimeManagerEndpoint, ""+
|
||||
"OpenPitrix runtime manager endpoint")
|
||||
// if s3-endpoint if left empty, following options will be ignored
|
||||
fs.StringVar(&s.S3Options.Endpoint, "openpitrix-s3-endpoint", c.S3Options.Endpoint, ""+
|
||||
"Endpoint to access to s3 object storage service for openpitrix, if left blank, the following options "+
|
||||
"will be ignored.")
|
||||
|
||||
fs.StringVar(&s.AppManagerEndpoint, "openpitrix-app-manager-endpoint", c.AppManagerEndpoint, ""+
|
||||
"OpenPitrix app manager endpoint")
|
||||
fs.StringVar(&s.S3Options.Region, "openpitrix-s3-region", c.S3Options.Region, ""+
|
||||
"Region of s3 that openpitrix will access to, like us-east-1.")
|
||||
|
||||
fs.StringVar(&s.ClusterManagerEndpoint, "openpitrix-cluster-manager-endpoint", c.ClusterManagerEndpoint, ""+
|
||||
"OpenPitrix cluster manager endpoint")
|
||||
fs.StringVar(&s.S3Options.AccessKeyID, "openpitrix-s3-access-key-id", c.S3Options.AccessKeyID, "access key of openpitrix s3")
|
||||
|
||||
fs.StringVar(&s.CategoryManagerEndpoint, "openpitrix-category-manager-endpoint", c.CategoryManagerEndpoint, ""+
|
||||
"OpenPitrix category manager endpoint")
|
||||
fs.StringVar(&s.S3Options.SecretAccessKey, "openpitrix-s3-secret-access-key", c.S3Options.SecretAccessKey, "secret access key of openpitrix s3")
|
||||
|
||||
fs.StringVar(&s.RepoManagerEndpoint, "openpitrix-repo-manager-endpoint", c.RepoManagerEndpoint, ""+
|
||||
"OpenPitrix repo manager endpoint")
|
||||
fs.StringVar(&s.S3Options.SessionToken, "openpitrix-s3-session-token", c.S3Options.SessionToken, "session token of openpitrix s3")
|
||||
|
||||
fs.StringVar(&s.RepoIndexerEndpoint, "openpitrix-repo-indexer-endpoint", c.RepoIndexerEndpoint, ""+
|
||||
"OpenPitrix repo indexer endpoint")
|
||||
fs.StringVar(&s.S3Options.Bucket, "openpitrix-s3-bucket", c.S3Options.Bucket, "bucket name of openpitrix s3")
|
||||
|
||||
fs.StringVar(&s.AttachmentManagerEndpoint, "openpitrix-attachment-manager-endpoint", c.AttachmentManagerEndpoint, ""+
|
||||
"OpenPitrix attachment manager endpoint")
|
||||
fs.BoolVar(&s.S3Options.DisableSSL, "openpitrix-s3-disable-SSL", c.S3Options.DisableSSL, "disable ssl")
|
||||
|
||||
fs.BoolVar(&s.S3Options.ForcePathStyle, "openpitrix-s3-force-path-style", c.S3Options.ForcePathStyle, "force path style")
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
"github.com/aws/aws-sdk-go/service/s3"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
type FakeS3 struct {
|
||||
@@ -61,3 +62,14 @@ func (s *FakeS3) Delete(key string) error {
|
||||
delete(s.Storage, key)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *FakeS3) Read(key string) ([]byte, error) {
|
||||
if o, ok := s.Storage[key]; ok && o.Body != nil {
|
||||
data, err := ioutil.ReadAll(o.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
return nil, awserr.New(s3.ErrCodeNoSuchKey, "no such object", nil)
|
||||
}
|
||||
|
||||
@@ -21,6 +21,9 @@ import (
|
||||
)
|
||||
|
||||
type Interface interface {
|
||||
//read the content, caller should close the io.ReadCloser.
|
||||
Read(key string) ([]byte, error)
|
||||
|
||||
// Upload uploads a object to storage and returns object location if succeeded
|
||||
Upload(key, fileName string, body io.Reader) error
|
||||
|
||||
|
||||
@@ -49,6 +49,24 @@ func (s *Client) Upload(key, fileName string, body io.Reader) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Client) Read(key string) ([]byte, error) {
|
||||
|
||||
downloader := s3manager.NewDownloader(s.s3Session)
|
||||
|
||||
writer := aws.NewWriteAtBuffer([]byte{})
|
||||
_, err := downloader.Download(writer,
|
||||
&s3.GetObjectInput{
|
||||
Bucket: aws.String(s.bucket),
|
||||
Key: aws.String(key),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return writer.Bytes(), nil
|
||||
}
|
||||
|
||||
func (s *Client) GetDownloadURL(key string, fileName string) (string, error) {
|
||||
req, _ := s.s3Client.GetObjectRequest(&s3.GetObjectInput{
|
||||
Bucket: aws.String(s.bucket),
|
||||
|
||||
Reference in New Issue
Block a user