diff --git a/.travis.yml b/.travis.yml index d518c2774..3c1358d75 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,8 +9,7 @@ git: depth: false go: - - "1.12.x" - - "tip" + - "1.13.x" env: - GO111MODULE=on cache: @@ -27,7 +26,7 @@ script: - make all before_install: - - go get -t -v ./... + - go mod vendor after_success: - bash <(curl -s https://codecov.io/bash) diff --git a/pkg/apiserver/registries/registries.go b/pkg/apiserver/registries/registries.go index ef785013a..4a8ec0138 100644 --- a/pkg/apiserver/registries/registries.go +++ b/pkg/apiserver/registries/registries.go @@ -21,6 +21,7 @@ package registries import ( "github.com/emicklei/go-restful" "net/http" + "strings" "kubesphere.io/kubesphere/pkg/models/registries" "kubesphere.io/kubesphere/pkg/server/errors" @@ -68,6 +69,25 @@ func RegistryImageBlob(request *restful.Request, response *restful.Response) { return } + // default use ssl + checkSSl := func(serverAddress string) bool { + if strings.HasPrefix(serverAddress, "http://") { + return false + } else { + return true + } + } + + if strings.HasPrefix(imageName, "http") { + dockerurl, err := registries.ParseDockerURL(imageName) + if err != nil { + log.Errorf("%+v", err) + errors.ParseSvcErr(restful.NewError(http.StatusBadRequest, err.Error()), response) + return + } + imageName = dockerurl.StringWithoutScheme() + } + // parse image image, err := registries.ParseImage(imageName) if err != nil { @@ -76,8 +96,10 @@ func RegistryImageBlob(request *restful.Request, response *restful.Response) { return } + useSSL := checkSSl(entry.ServerAddress) + // Create the registry client. - r, err := registries.CreateRegistryClient(entry.Username, entry.Password, image.Domain) + r, err := registries.CreateRegistryClient(entry.Username, entry.Password, image.Domain, useSSL) if err != nil { log.Errorf("%+v", err) response.WriteAsJson(®istries.ImageDetails{Status: registries.StatusFailed, Message: err.Error()}) diff --git a/pkg/apiserver/resources/storage.go b/pkg/apiserver/resources/storage.go index 330aa3668..41ba4cc67 100644 --- a/pkg/apiserver/resources/storage.go +++ b/pkg/apiserver/resources/storage.go @@ -20,8 +20,10 @@ package resources import ( "github.com/emicklei/go-restful" "k8s.io/api/core/v1" + "k8s.io/klog" "net/http" + storagev1 "k8s.io/api/storage/v1" "kubesphere.io/kubesphere/pkg/models/storage" "kubesphere.io/kubesphere/pkg/server/errors" ) @@ -69,3 +71,26 @@ func GetPvcListBySc(request *restful.Request, response *restful.Response) { response.WriteAsJson(result) } + +func PatchStorageClass(request *restful.Request, response *restful.Response) { + scObj := &storagev1.StorageClass{} + err := request.ReadEntity(scObj) + if err != nil { + klog.Errorf("read entity error: %s", err.Error()) + response.WriteHeaderAndEntity(http.StatusBadRequest, errors.Wrap(err)) + return + } + klog.V(5).Infof("succeed to read entity %v", scObj) + scName := request.PathParameter("storageclass") + if scObj.Annotations[storage.IsDefaultStorageClassAnnotation] == "true" { + // Set default storage class + sc, err := storage.SetDefaultStorageClass(scName) + if err != nil { + response.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err)) + return + } + response.WriteEntity(sc) + return + } + response.WriteEntity(errors.None) +} diff --git a/pkg/kapis/resources/v1alpha2/register.go b/pkg/kapis/resources/v1alpha2/register.go index 5568aa02a..2e183efed 100644 --- a/pkg/kapis/resources/v1alpha2/register.go +++ b/pkg/kapis/resources/v1alpha2/register.go @@ -22,6 +22,7 @@ import ( "github.com/emicklei/go-restful-openapi" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + storagev1 "k8s.io/api/storage/v1" "k8s.io/apimachinery/pkg/runtime/schema" "kubesphere.io/kubesphere/pkg/apiserver/components" "kubesphere.io/kubesphere/pkg/apiserver/git" @@ -257,7 +258,14 @@ func addWebService(c *restful.Container) error { Returns(http.StatusOK, ok, status.WorkLoadStatus{}). To(workloadstatuses.GetNamespacedAbnormalWorkloads)) - c.Add(webservice) + webservice.Route(webservice.PATCH("/storageclasses/{storageclass}"). + To(resources.PatchStorageClass). + Doc("patch storage class"). + Metadata(restfulspec.KeyOpenAPITags, []string{constants.ClusterResourcesTag}). + Returns(http.StatusOK, ok, storagev1.StorageClass{}). + Writes(storagev1.StorageClass{}). + Param(webservice.PathParameter("storageclass", "the name of storage class"))) + c.Add(webservice) return nil } diff --git a/pkg/models/registries/image.go b/pkg/models/registries/image.go index 1d5f725be..fd32f35fe 100644 --- a/pkg/models/registries/image.go +++ b/pkg/models/registries/image.go @@ -2,9 +2,11 @@ package registries import ( "fmt" - "github.com/docker/distribution/reference" - digest "github.com/opencontainers/go-digest" + "github.com/opencontainers/go-digest" + log "k8s.io/klog" + "net/url" + "strings" ) // Image holds information about an image. @@ -30,9 +32,28 @@ func (i *Image) Reference() string { return i.Tag } +type DockerURL struct { + *url.URL +} + +func (u *DockerURL) StringWithoutScheme() string { + u.Scheme = "" + s := u.String() + return strings.Trim(s, "//") +} + +func ParseDockerURL(rawurl string) (*DockerURL, error) { + url, err := url.Parse(rawurl) + if err != nil { + log.Errorf("%+v", err) + return nil, err + } + return &DockerURL{URL: url}, nil +} + // ParseImage returns an Image struct with all the values filled in for a given image. // example : localhost:5000/nginx:latest, nginx:perl etc. -func ParseImage(image string) (Image, error) { +func ParseImage(image string) (i Image, err error) { // Parse the image name and tag. named, err := reference.ParseNormalizedNamed(image) if err != nil { @@ -41,7 +62,7 @@ func ParseImage(image string) (Image, error) { // Add the latest lag if they did not provide one. named = reference.TagNameOnly(named) - i := Image{ + i = Image{ named: named, Domain: reference.Domain(named), Path: reference.Path(named), diff --git a/pkg/models/registries/image_test.go b/pkg/models/registries/image_test.go new file mode 100644 index 000000000..dd8d8af73 --- /dev/null +++ b/pkg/models/registries/image_test.go @@ -0,0 +1,73 @@ +package registries + +import ( + "testing" +) + +func TestParseImage(t *testing.T) { + type testImage struct { + inputImageName string + ExImage Image + } + + testImages := []testImage{ + {inputImageName: "dockerhub.qingcloud.com/kubesphere/test:v1", ExImage: Image{Domain: "dockerhub.qingcloud.com", Tag: "v1", Path: "kubesphere/test"}}, + {inputImageName: "harbor.devops.kubesphere.local:30280/library/tomcat:latest", ExImage: Image{Domain: "harbor.devops.kubesphere.local:30280", Tag: "latest", Path: "library/tomcat"}}, + {inputImageName: "zhuxiaoyang/nginx:v1", ExImage: Image{Domain: "docker.io", Tag: "v1", Path: "zhuxiaoyang/nginx"}}, + {inputImageName: "nginx", ExImage: Image{Domain: "docker.io", Tag: "latest", Path: "library/nginx"}}, + {inputImageName: "nginx:latest", ExImage: Image{Domain: "docker.io", Tag: "latest", Path: "library/nginx"}}, + {inputImageName: "kubesphere/ks-account:v2.1.0", ExImage: Image{Domain: "docker.io", Tag: "v2.1.0", Path: "kubesphere/ks-account"}}, + {inputImageName: "http://docker.io/nginx:latest", ExImage: Image{}}, + {inputImageName: "https://harbor.devops.kubesphere.local:30280/library/tomcat:latest", ExImage: Image{}}, + {inputImageName: "docker.io/nginx:latest:latest", ExImage: Image{}}, + {inputImageName: "nginx:8000:latest", ExImage: Image{}}, + } + + for _, image := range testImages { + res, err := ParseImage(image.inputImageName) + if err != nil { + if res != image.ExImage { + t.Fatalf("Get err %s", err) + } + } + if res.Domain != image.ExImage.Domain { + t.Fatalf("Doamin got %v, expected %v", res.Domain, image.ExImage.Domain) + } + + if res.Tag != image.ExImage.Tag { + t.Fatalf("Tag got %v, expected %v", res.Tag, image.ExImage.Tag) + } + + if res.Path != image.ExImage.Path { + t.Fatalf("Path got %v, expected %v", res.Path, image.ExImage.Path) + } + + } + +} + +func TestStringWithoutScheme(t *testing.T) { + type testRawUrl struct { + Rawurl string + ExUrl string + } + testRawurls := []testRawUrl{ + {"http://dockerhub.qingcloud.com/kubesphere/nginx:v1", "dockerhub.qingcloud.com/kubesphere/nginx:v1"}, + {"https://dockerhub.qingcloud.com/kubesphere/nginx:v1", "dockerhub.qingcloud.com/kubesphere/nginx:v1"}, + {"http://harbor.devops.kubesphere.local:30280/library/tomcat:latest", "harbor.devops.kubesphere.local:30280/library/tomcat:latest"}, + {"https://harbor.devops.kubesphere.local:30280/library/tomcat:latest", "harbor.devops.kubesphere.local:30280/library/tomcat:latest"}, + } + + for _, rawurl := range testRawurls { + dockerurl, err := ParseDockerURL(rawurl.Rawurl) + if err != nil { + t.Fatalf("Get err %s", err) + } + + imageName := dockerurl.StringWithoutScheme() + + if imageName != rawurl.ExUrl { + t.Fatalf("imagename got %v, expected %v", imageName, rawurl.ExUrl) + } + } +} diff --git a/pkg/models/registries/manifest_test.go b/pkg/models/registries/manifest_test.go index 7afad1cce..2b52939cf 100644 --- a/pkg/models/registries/manifest_test.go +++ b/pkg/models/registries/manifest_test.go @@ -7,7 +7,7 @@ import ( func TestDigestFromDockerHub(t *testing.T) { testImage := Image{Domain: "docker.io", Path: "library/alpine", Tag: "latest"} - r, err := CreateRegistryClient("", "", "docker.io") + r, err := CreateRegistryClient("", "", "docker.io", true) if err != nil { t.Fatalf("Could not get client: %s", err) } diff --git a/pkg/models/registries/registry_client.go b/pkg/models/registries/registry_client.go index 5cceb3a31..3a18eac1f 100644 --- a/pkg/models/registries/registry_client.go +++ b/pkg/models/registries/registry_client.go @@ -64,7 +64,7 @@ type authService struct { Scope []string } -func CreateRegistryClient(username, password, domain string) (*Registry, error) { +func CreateRegistryClient(username, password, domain string, useSSL bool) (*Registry, error) { authDomain := domain auth, err := GetAuthConfig(username, password, authDomain) if err != nil { @@ -75,6 +75,7 @@ func CreateRegistryClient(username, password, domain string) (*Registry, error) // Create the registry client. return New(auth, RegistryOpt{ Domain: domain, + UseSSL: useSSL, }) } diff --git a/pkg/models/registries/registry_client_test.go b/pkg/models/registries/registry_client_test.go index ad7da03fa..d48647dec 100644 --- a/pkg/models/registries/registry_client_test.go +++ b/pkg/models/registries/registry_client_test.go @@ -4,6 +4,8 @@ import ( "testing" ) +const DockerHub = "docker.io" + func TestCreateRegistryClient(t *testing.T) { type imageInfo struct { Username string @@ -11,17 +13,19 @@ func TestCreateRegistryClient(t *testing.T) { Domain string ExDomain string ExUrl string + UseSSL bool } testImages := []imageInfo{ - {Domain: "kubesphere.io", ExDomain: "kubesphere.io", ExUrl: "http://kubesphere.io"}, - {Domain: "127.0.0.1:5000", ExDomain: "127.0.0.1:5000", ExUrl: "http://127.0.0.1:5000"}, - {Username: "Username", Password: "Password", Domain: "docker.io", ExDomain: "registry-1.docker.io", ExUrl: "https://registry-1.docker.io"}, - {Domain: "harbor.devops.kubesphere.local:30280", ExDomain: "harbor.devops.kubesphere.local:30280", ExUrl: "http://harbor.devops.kubesphere.local:30280"}, + {Domain: "kubesphere.io", ExDomain: "kubesphere.io", ExUrl: "https://kubesphere.io", UseSSL: true}, + {Domain: "127.0.0.1:5000", ExDomain: "127.0.0.1:5000", ExUrl: "http://127.0.0.1:5000", UseSSL: false}, + {Username: "Username", Password: "Password", Domain: DockerHub, ExDomain: "registry-1.docker.io", ExUrl: "https://registry-1.docker.io", UseSSL: true}, + {Domain: "harbor.devops.kubesphere.local:30280", ExDomain: "harbor.devops.kubesphere.local:30280", ExUrl: "http://harbor.devops.kubesphere.local:30280", UseSSL: false}, + {Domain: "dockerhub.qingcloud.com/zxytest/s2i-jj:jj", ExDomain: "dockerhub.qingcloud.com", ExUrl: "https://dockerhub.qingcloud.com/zxytest/s2i-jj:jj", UseSSL: true}, } for _, testImage := range testImages { - reg, err := CreateRegistryClient(testImage.Username, testImage.Password, testImage.Domain) + reg, err := CreateRegistryClient(testImage.Username, testImage.Password, testImage.Domain, testImage.UseSSL) if err != nil { t.Fatalf("Get err %s", err) } @@ -36,8 +40,8 @@ func TestCreateRegistryClient(t *testing.T) { } - testImage := Image{Domain: "docker.io", Path: "library/alpine", Tag: "latest"} - r, err := CreateRegistryClient("", "", "docker.io") + testImage := Image{Domain: DockerHub, Path: "library/alpine", Tag: "latest"} + r, err := CreateRegistryClient("", "", DockerHub, true) if err != nil { t.Fatalf("Could not get client: %s", err) } diff --git a/pkg/models/registries/token_test.go b/pkg/models/registries/token_test.go index ea231472e..68ea0cd34 100644 --- a/pkg/models/registries/token_test.go +++ b/pkg/models/registries/token_test.go @@ -32,7 +32,7 @@ func (asm authServiceMock) equalTo(v *authService) bool { func TestToken(t *testing.T) { testImage := Image{Domain: "docker.io", Path: "library/alpine", Tag: "latest"} - r, err := CreateRegistryClient("", "", "docker.io") + r, err := CreateRegistryClient("", "", "docker.io", true) if err != nil { t.Fatalf("Could not get registry client: %s", err) } diff --git a/pkg/models/storage/storage.go b/pkg/models/storage/storage.go index 4c784defb..4958d3c4a 100644 --- a/pkg/models/storage/storage.go +++ b/pkg/models/storage/storage.go @@ -18,6 +18,7 @@ package storage import ( + "kubesphere.io/kubesphere/pkg/simple/client" "strconv" "k8s.io/api/core/v1" @@ -27,16 +28,17 @@ import ( "kubesphere.io/kubesphere/pkg/informers" ) +const ( + IsDefaultStorageClassAnnotation = "storageclass.kubernetes.io/is-default-class" + betaIsDefaultStorageClassAnnotation = "storageclass.beta.kubernetes.io/is-default-class" +) + type ScMetrics struct { Capacity string `json:"capacity,omitempty"` Usage string `json:"usage,omitempty"` PvcNumber string `json:"pvcNumber"` } -func init() { - -} - func GetPvcListBySc(scName string) ([]*v1.PersistentVolumeClaim, error) { persistentVolumeClaimLister := informers.SharedInformerFactory().Core().V1().PersistentVolumeClaims().Lister() all, err := persistentVolumeClaimLister.List(labels.Everything()) @@ -102,3 +104,36 @@ func GetScList() ([]*storageV1.StorageClass, error) { return scList, nil } + +func SetDefaultStorageClass(defaultScName string) (*storageV1.StorageClass, error) { + scLister := informers.SharedInformerFactory().Storage().V1().StorageClasses().Lister() + // 1. verify storage class name + sc, err := scLister.Get(defaultScName) + if sc == nil || err != nil { + return sc, err + } + // 2. unset all default sc and then set default sc + scList, err := scLister.List(labels.Everything()) + if err != nil { + return nil, err + } + k8sClient := client.ClientSets().K8s().Kubernetes() + var defaultSc *storageV1.StorageClass + for _, sc := range scList { + _, hasDefault := sc.Annotations[IsDefaultStorageClassAnnotation] + _, hasBeta := sc.Annotations[betaIsDefaultStorageClassAnnotation] + if sc.Name == defaultScName || hasDefault || hasBeta { + delete(sc.Annotations, IsDefaultStorageClassAnnotation) + delete(sc.Annotations, betaIsDefaultStorageClassAnnotation) + if sc.Name == defaultScName { + sc.Annotations[IsDefaultStorageClassAnnotation] = "true" + defaultSc = sc + } + _, err := k8sClient.StorageV1().StorageClasses().Update(sc) + if err != nil { + return nil, err + } + } + } + return defaultSc, nil +}