mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-24 01:27:49 +01:00
Merge pull request #3761 from vmware/add_api_testing
[Skip CI]add api testing for harbor including image pull and push
This commit is contained in:
commit
9193161f25
97
tests/apitests/api-testing/client/docker_client.go
Normal file
97
tests/apitests/api-testing/client/docker_client.go
Normal file
@ -0,0 +1,97 @@
|
||||
package client
|
||||
|
||||
import "os/exec"
|
||||
import "strings"
|
||||
import "errors"
|
||||
import "bufio"
|
||||
import "fmt"
|
||||
|
||||
//DockerClient : Run docker commands
|
||||
type DockerClient struct{}
|
||||
|
||||
//Status : Check if docker daemon is there
|
||||
func (dc *DockerClient) Status() error {
|
||||
cmdName := "docker"
|
||||
args := []string{"info"}
|
||||
|
||||
return dc.runCommand(cmdName, args)
|
||||
}
|
||||
|
||||
//Pull : Pull image
|
||||
func (dc *DockerClient) Pull(image string) error {
|
||||
if len(strings.TrimSpace(image)) == 0 {
|
||||
return errors.New("Empty image")
|
||||
}
|
||||
|
||||
cmdName := "docker"
|
||||
args := []string{"pull", image}
|
||||
|
||||
return dc.runCommandWithOutput(cmdName, args)
|
||||
}
|
||||
|
||||
//Tag :Tag image
|
||||
func (dc *DockerClient) Tag(source, target string) error {
|
||||
if len(strings.TrimSpace(source)) == 0 ||
|
||||
len(strings.TrimSpace(target)) == 0 {
|
||||
return errors.New("Empty images")
|
||||
}
|
||||
|
||||
cmdName := "docker"
|
||||
args := []string{"tag", source, target}
|
||||
|
||||
return dc.runCommandWithOutput(cmdName, args)
|
||||
}
|
||||
|
||||
//Push : push image
|
||||
func (dc *DockerClient) Push(image string) error {
|
||||
if len(strings.TrimSpace(image)) == 0 {
|
||||
return errors.New("Empty image")
|
||||
}
|
||||
|
||||
cmdName := "docker"
|
||||
args := []string{"push", image}
|
||||
|
||||
return dc.runCommandWithOutput(cmdName, args)
|
||||
}
|
||||
|
||||
//Login : Login docker
|
||||
func (dc *DockerClient) Login(userName, password string, uri string) error {
|
||||
if len(strings.TrimSpace(userName)) == 0 ||
|
||||
len(strings.TrimSpace(password)) == 0 {
|
||||
return errors.New("Invlaid credential")
|
||||
}
|
||||
|
||||
cmdName := "docker"
|
||||
args := []string{"login", "-u", userName, "-p", password, uri}
|
||||
|
||||
return dc.runCommandWithOutput(cmdName, args)
|
||||
}
|
||||
|
||||
func (dc *DockerClient) runCommand(cmdName string, args []string) error {
|
||||
return exec.Command(cmdName, args...).Run()
|
||||
}
|
||||
|
||||
func (dc *DockerClient) runCommandWithOutput(cmdName string, args []string) error {
|
||||
cmd := exec.Command(cmdName, args...)
|
||||
cmdReader, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(cmdReader)
|
||||
go func() {
|
||||
for scanner.Scan() {
|
||||
fmt.Printf("%s out | %s\n", cmdName, scanner.Text())
|
||||
}
|
||||
}()
|
||||
|
||||
if err = cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = cmd.Wait(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
170
tests/apitests/api-testing/client/harbor_api_client.go
Normal file
170
tests/apitests/api-testing/client/harbor_api_client.go
Normal file
@ -0,0 +1,170 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
httpHeaderJSON = "application/json"
|
||||
httpHeaderContentType = "Content-Type"
|
||||
httpHeaderAccept = "Accept"
|
||||
)
|
||||
|
||||
//APIClientConfig : Keep config options for APIClient
|
||||
type APIClientConfig struct {
|
||||
Username string
|
||||
Password string
|
||||
CaFile string
|
||||
CertFile string
|
||||
KeyFile string
|
||||
}
|
||||
|
||||
//APIClient provided the http client for trigger http requests
|
||||
type APIClient struct {
|
||||
//http client
|
||||
client *http.Client
|
||||
|
||||
//Configuration
|
||||
config APIClientConfig
|
||||
}
|
||||
|
||||
//NewAPIClient is constructor of APIClient
|
||||
func NewAPIClient(config APIClientConfig) (*APIClient, error) {
|
||||
//Load client cert
|
||||
cert, err := tls.LoadX509KeyPair(config.CertFile, config.KeyFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//Add ca
|
||||
caCert, err := ioutil.ReadFile(config.CaFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
caCertPool := x509.NewCertPool()
|
||||
caCertPool.AppendCertsFromPEM(caCert)
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
RootCAs: caCertPool,
|
||||
}
|
||||
|
||||
tlsConfig.BuildNameToCertificate()
|
||||
transport := &http.Transport{
|
||||
TLSClientConfig: tlsConfig,
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Transport: transport,
|
||||
}
|
||||
|
||||
return &APIClient{
|
||||
client: client,
|
||||
config: config,
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
//Get data
|
||||
func (ac *APIClient) Get(url string) ([]byte, error) {
|
||||
if strings.TrimSpace(url) == "" {
|
||||
return nil, errors.New("empty url")
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set(httpHeaderAccept, httpHeaderJSON)
|
||||
req.SetBasicAuth(ac.config.Username, ac.config.Password)
|
||||
|
||||
resp, err := ac.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if resp.Body != nil {
|
||||
resp.Body.Close()
|
||||
}
|
||||
}()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, errors.New(resp.Status)
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
//Post data
|
||||
func (ac *APIClient) Post(url string, data []byte) error {
|
||||
if strings.TrimSpace(url) == "" {
|
||||
return errors.New("Empty url")
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", url, strings.NewReader(string(data)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set(httpHeaderContentType, httpHeaderJSON)
|
||||
req.SetBasicAuth(ac.config.Username, ac.config.Password)
|
||||
resp, err := ac.client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusCreated &&
|
||||
resp.StatusCode != http.StatusOK {
|
||||
return errors.New(resp.Status)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//Delete data
|
||||
func (ac *APIClient) Delete(url string) error {
|
||||
if strings.TrimSpace(url) == "" {
|
||||
return errors.New("Empty url")
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("DELETE", url, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set(httpHeaderAccept, httpHeaderJSON)
|
||||
req.SetBasicAuth(ac.config.Username, ac.config.Password)
|
||||
|
||||
resp, err := ac.client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return errors.New(resp.Status)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//SwitchAccount : Switch account
|
||||
func (ac *APIClient) SwitchAccount(username, password string) {
|
||||
if len(strings.TrimSpace(username)) == 0 ||
|
||||
len(strings.TrimSpace(password)) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
ac.config.Username = username
|
||||
ac.config.Password = password
|
||||
}
|
17
tests/apitests/api-testing/envs/concourse_ci.go
Normal file
17
tests/apitests/api-testing/envs/concourse_ci.go
Normal file
@ -0,0 +1,17 @@
|
||||
package envs
|
||||
|
||||
//ConcourseCIEnv : Env for concourse pipeline
|
||||
var ConcourseCIEnv = Environment{
|
||||
Protocol: "https",
|
||||
TestingProject: "concoursecitesting01",
|
||||
ImageName: "busybox",
|
||||
ImageTag: "latest",
|
||||
CAFile: "../../../ca.crt",
|
||||
KeyFile: "../../../key.crt",
|
||||
CertFile: "../../../cert.crt",
|
||||
Account: "cody",
|
||||
Password: "Admin!23",
|
||||
Admin: "admin",
|
||||
AdminPass: "pksxgxmifc0cnwa5px9h",
|
||||
Hostname: "10.112.122.1",
|
||||
}
|
127
tests/apitests/api-testing/envs/environment.go
Normal file
127
tests/apitests/api-testing/envs/environment.go
Normal file
@ -0,0 +1,127 @@
|
||||
package envs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/vmware/harbor/tests/apitests/api-testing/client"
|
||||
)
|
||||
|
||||
//Environment keeps the testing env info
|
||||
type Environment struct {
|
||||
Protocol string //env var: HTTP_PROTOCOL
|
||||
Hostname string //env var: TESTING_ENV_HOSTNAME
|
||||
Account string //env var: TESTING_ENV_ACCOUNT
|
||||
Password string //env var: TESTING_ENV_PASSWORD
|
||||
Admin string //env var: TESTING_ENV_ADMIN
|
||||
AdminPass string //env var: TESTING_ENV_ADMIN_PASS
|
||||
TestingProject string //env var: TESTING_PROJECT_NAME
|
||||
ImageName string //env var: TESTING_IMAGE_NAME
|
||||
ImageTag string //env var: TESTING_IMAGE_TAG
|
||||
CAFile string //env var: CA_FILE_PATH
|
||||
CertFile string //env var: CERT_FILE_PATH
|
||||
KeyFile string //env var: KEY_FILE_PATH
|
||||
|
||||
//API client
|
||||
HTTPClient *client.APIClient
|
||||
|
||||
//Docker client
|
||||
DockerClient *client.DockerClient
|
||||
|
||||
//Initialize status
|
||||
loaded bool
|
||||
}
|
||||
|
||||
//Load test env info
|
||||
func (env *Environment) Load() error {
|
||||
host := os.Getenv("TESTING_ENV_HOSTNAME")
|
||||
if isNotEmpty(host) {
|
||||
env.Hostname = host
|
||||
}
|
||||
|
||||
account := os.Getenv("TESTING_ENV_ACCOUNT")
|
||||
if isNotEmpty(account) {
|
||||
env.Account = account
|
||||
}
|
||||
|
||||
pwd := os.Getenv("TESTING_ENV_PASSWORD")
|
||||
if isNotEmpty(pwd) {
|
||||
env.Password = pwd
|
||||
}
|
||||
|
||||
admin := os.Getenv("TESTING_ENV_ADMIN")
|
||||
if isNotEmpty(admin) {
|
||||
env.Admin = admin
|
||||
}
|
||||
|
||||
adminPwd := os.Getenv("TESTING_ENV_ADMIN_PASS")
|
||||
if isNotEmpty(adminPwd) {
|
||||
env.AdminPass = adminPwd
|
||||
}
|
||||
|
||||
pro := os.Getenv("TESTING_PROJECT_NAME")
|
||||
if isNotEmpty(pro) {
|
||||
env.TestingProject = pro
|
||||
}
|
||||
|
||||
imgName := os.Getenv("TESTING_IMAGE_NAME")
|
||||
if isNotEmpty(imgName) {
|
||||
env.ImageName = imgName
|
||||
}
|
||||
|
||||
imgTag := os.Getenv("TESTING_IMAGE_TAG")
|
||||
if isNotEmpty(imgTag) {
|
||||
env.ImageTag = imgTag
|
||||
}
|
||||
|
||||
protocol := os.Getenv("HTTP_PROTOCOL")
|
||||
if isNotEmpty(protocol) {
|
||||
env.Protocol = protocol
|
||||
}
|
||||
|
||||
caFile := os.Getenv("CA_FILE_PATH")
|
||||
if isNotEmpty(caFile) {
|
||||
env.CAFile = caFile
|
||||
}
|
||||
|
||||
keyFile := os.Getenv("KEY_FILE_PATH")
|
||||
if isNotEmpty(keyFile) {
|
||||
env.KeyFile = keyFile
|
||||
}
|
||||
|
||||
certFile := os.Getenv("CERT_FILE_PATH")
|
||||
if isNotEmpty(certFile) {
|
||||
env.CertFile = certFile
|
||||
}
|
||||
|
||||
if !env.loaded {
|
||||
cfg := client.APIClientConfig{
|
||||
Username: env.Admin,
|
||||
Password: env.AdminPass,
|
||||
CaFile: env.CAFile,
|
||||
CertFile: env.CertFile,
|
||||
KeyFile: env.KeyFile,
|
||||
}
|
||||
|
||||
httpClient, err := client.NewAPIClient(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
env.HTTPClient = httpClient
|
||||
env.DockerClient = &client.DockerClient{}
|
||||
|
||||
env.loaded = true
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//RootURI : The root URI like https://<hostname>
|
||||
func (env *Environment) RootURI() string {
|
||||
return fmt.Sprintf("%s://%s", env.Protocol, env.Hostname)
|
||||
}
|
||||
|
||||
func isNotEmpty(str string) bool {
|
||||
return len(strings.TrimSpace(str)) > 0
|
||||
}
|
137
tests/apitests/api-testing/lib/image.go
Normal file
137
tests/apitests/api-testing/lib/image.go
Normal file
@ -0,0 +1,137 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/vmware/harbor/tests/apitests/api-testing/client"
|
||||
"github.com/vmware/harbor/tests/apitests/api-testing/models"
|
||||
)
|
||||
|
||||
//ImageUtil : For repository and tag functions
|
||||
type ImageUtil struct {
|
||||
rootURI string
|
||||
testingClient *client.APIClient
|
||||
}
|
||||
|
||||
//NewImageUtil : Constructor
|
||||
func NewImageUtil(rootURI string, httpClient *client.APIClient) *ImageUtil {
|
||||
if len(strings.TrimSpace(rootURI)) == 0 || httpClient == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &ImageUtil{
|
||||
rootURI: rootURI,
|
||||
testingClient: httpClient,
|
||||
}
|
||||
}
|
||||
|
||||
//DeleteRepo : Delete repo
|
||||
func (iu *ImageUtil) DeleteRepo(repoName string) error {
|
||||
if len(strings.TrimSpace(repoName)) == 0 {
|
||||
return errors.New("Empty repo name for deleting")
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s%s%s", iu.rootURI, "/api/repositories/", repoName)
|
||||
if err := iu.testingClient.Delete(url); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//ScanTag :Scan a tag
|
||||
func (iu *ImageUtil) ScanTag(repoName string, tagName string) error {
|
||||
if len(strings.TrimSpace(repoName)) == 0 {
|
||||
return errors.New("Empty repo name for scanning")
|
||||
}
|
||||
|
||||
if len(strings.TrimSpace(tagName)) == 0 {
|
||||
return errors.New("Empty tag name for scanning")
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s%s%s%s%s%s", iu.rootURI, "/api/repositories/", repoName, "/tags/", tagName, "/scan")
|
||||
if err := iu.testingClient.Post(url, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tk := time.NewTicker(1 * time.Second)
|
||||
defer tk.Stop()
|
||||
done := make(chan bool)
|
||||
errchan := make(chan error)
|
||||
url = fmt.Sprintf("%s%s%s%s%s", iu.rootURI, "/api/repositories/", repoName, "/tags/", tagName)
|
||||
go func() {
|
||||
for _ = range tk.C {
|
||||
data, err := iu.testingClient.Get(url)
|
||||
if err != nil {
|
||||
errchan <- err
|
||||
return
|
||||
}
|
||||
var tag models.Tag
|
||||
if err = json.Unmarshal(data, &tag); err != nil {
|
||||
errchan <- err
|
||||
return
|
||||
}
|
||||
|
||||
if tag.ScanOverview != nil && tag.ScanOverview.Status == "finished" {
|
||||
done <- true
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
return nil
|
||||
case <-time.After(20 * time.Second):
|
||||
return errors.New("Scan timeout after 30 seconds")
|
||||
}
|
||||
}
|
||||
|
||||
//GetRepos : Get repos in the project
|
||||
func (iu *ImageUtil) GetRepos(projectName string) ([]models.Repository, error) {
|
||||
if len(strings.TrimSpace(projectName)) == 0 {
|
||||
return nil, errors.New("Empty project name for getting repos")
|
||||
}
|
||||
|
||||
proUtil := NewProjectUtil(iu.rootURI, iu.testingClient)
|
||||
pid := proUtil.GetProjectID(projectName)
|
||||
if pid == -1 {
|
||||
return nil, fmt.Errorf("Failed to get project ID with name %s", projectName)
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s%s%d", iu.rootURI, "/api/repositories?project_id=", pid)
|
||||
data, err := iu.testingClient.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var repos []models.Repository
|
||||
if err = json.Unmarshal(data, &repos); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return repos, nil
|
||||
}
|
||||
|
||||
//GetTags : Get tags
|
||||
func (iu *ImageUtil) GetTags(repoName string) ([]models.Tag, error) {
|
||||
if len(strings.TrimSpace(repoName)) == 0 {
|
||||
return nil, errors.New("Empty repository name for getting tags")
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s%s%s%s", iu.rootURI, "/api/repositories/", repoName, "/tags")
|
||||
tagData, err := iu.testingClient.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var tags []models.Tag
|
||||
if err = json.Unmarshal(tagData, &tags); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tags, nil
|
||||
}
|
169
tests/apitests/api-testing/lib/project.go
Normal file
169
tests/apitests/api-testing/lib/project.go
Normal file
@ -0,0 +1,169 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/vmware/harbor/tests/apitests/api-testing/client"
|
||||
"github.com/vmware/harbor/tests/apitests/api-testing/models"
|
||||
)
|
||||
|
||||
//ProjectUtil : Util methods for project related
|
||||
type ProjectUtil struct {
|
||||
rootURI string
|
||||
testingClient *client.APIClient
|
||||
}
|
||||
|
||||
//NewProjectUtil : Constructor
|
||||
func NewProjectUtil(rootURI string, httpClient *client.APIClient) *ProjectUtil {
|
||||
if len(strings.TrimSpace(rootURI)) == 0 || httpClient == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &ProjectUtil{
|
||||
rootURI: rootURI,
|
||||
testingClient: httpClient,
|
||||
}
|
||||
}
|
||||
|
||||
//GetProjects : Get projects
|
||||
//If name specified, then only get the specified project
|
||||
func (pu *ProjectUtil) GetProjects(name string) ([]models.ExistingProject, error) {
|
||||
url := pu.rootURI + "/api/projects"
|
||||
if len(strings.TrimSpace(name)) > 0 {
|
||||
url = url + "?name=" + name
|
||||
}
|
||||
data, err := pu.testingClient.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var pros []models.ExistingProject
|
||||
if err = json.Unmarshal(data, &pros); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return pros, nil
|
||||
}
|
||||
|
||||
//GetProjectID : Get the project ID
|
||||
//If no project existing with the name, then return -1
|
||||
func (pu *ProjectUtil) GetProjectID(projectName string) int {
|
||||
pros, err := pu.GetProjects(projectName)
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
|
||||
if len(pros) == 0 {
|
||||
return -1
|
||||
}
|
||||
|
||||
for _, pro := range pros {
|
||||
if pro.Name == projectName {
|
||||
return pro.ID
|
||||
}
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
//CreateProject :Create project
|
||||
func (pu *ProjectUtil) CreateProject(projectName string, accessLevel bool) error {
|
||||
if len(strings.TrimSpace(projectName)) == 0 {
|
||||
return errors.New("Empty project name for creating")
|
||||
}
|
||||
|
||||
p := models.Project{
|
||||
Name: projectName,
|
||||
Metadata: &models.Metadata{
|
||||
AccessLevel: fmt.Sprintf("%v", accessLevel),
|
||||
},
|
||||
}
|
||||
|
||||
body, err := json.Marshal(&p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
url := pu.rootURI + "/api/projects"
|
||||
if err = pu.testingClient.Post(url, body); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//DeleteProject : Delete project
|
||||
func (pu *ProjectUtil) DeleteProject(projectName string) error {
|
||||
if len(strings.TrimSpace(projectName)) == 0 {
|
||||
return errors.New("Empty project name for deleting")
|
||||
}
|
||||
|
||||
pid := pu.GetProjectID(projectName)
|
||||
if pid == -1 {
|
||||
return errors.New("Failed to get project ID")
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s%s%d", pu.rootURI, "/api/projects/", pid)
|
||||
|
||||
if err := pu.testingClient.Delete(url); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//AssignRole : Assign role to user
|
||||
func (pu *ProjectUtil) AssignRole(projectName, username string) error {
|
||||
if len(strings.TrimSpace(projectName)) == 0 ||
|
||||
len(strings.TrimSpace(username)) == 0 {
|
||||
return errors.New("Project name and username are required for assigning role")
|
||||
}
|
||||
|
||||
pid := pu.GetProjectID(projectName)
|
||||
if pid == -1 {
|
||||
return fmt.Errorf("Failed to get project ID with name %s", projectName)
|
||||
}
|
||||
|
||||
m := models.Member{
|
||||
UserName: username,
|
||||
Roles: []int{2},
|
||||
}
|
||||
|
||||
body, err := json.Marshal(&m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s%s%d%s", pu.rootURI, "/api/projects/", pid, "/members")
|
||||
if err := pu.testingClient.Post(url, body); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//RevokeRole : RevokeRole role from user
|
||||
func (pu *ProjectUtil) RevokeRole(projectName string, uid int) error {
|
||||
if len(strings.TrimSpace(projectName)) == 0 {
|
||||
return errors.New("Project name is required for revoking role")
|
||||
}
|
||||
|
||||
if uid == 0 {
|
||||
return errors.New("User ID is required for revoking role")
|
||||
}
|
||||
|
||||
pid := pu.GetProjectID(projectName)
|
||||
if pid == -1 {
|
||||
return fmt.Errorf("Failed to get project ID with name %s", projectName)
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s%s%d%s%d", pu.rootURI, "/api/projects/", pid, "/members/", uid)
|
||||
if err := pu.testingClient.Delete(url); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
43
tests/apitests/api-testing/lib/report.go
Normal file
43
tests/apitests/api-testing/lib/report.go
Normal file
@ -0,0 +1,43 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Report struct {
|
||||
passed []string
|
||||
failed []string
|
||||
}
|
||||
|
||||
//Passed case
|
||||
func (r *Report) Passed(caseName string) {
|
||||
r.passed = append(r.passed, fmt.Sprintf("%s: [%s]", caseName, "PASSED"))
|
||||
}
|
||||
|
||||
//Failed case
|
||||
func (r *Report) Failed(caseName string, err error) {
|
||||
r.failed = append(r.failed, fmt.Sprintf("%s: [%s] %s", caseName, "FAILED", err.Error()))
|
||||
}
|
||||
|
||||
//Print report
|
||||
func (r *Report) Print() {
|
||||
passed := len(r.passed)
|
||||
failed := len(r.failed)
|
||||
total := passed + failed
|
||||
|
||||
fmt.Println("=====================================")
|
||||
fmt.Printf("Overall: %d/%d passed , %d/%d failed\n", passed, total, failed, total)
|
||||
fmt.Println("=====================================")
|
||||
for _, res := range r.passed {
|
||||
fmt.Println(res)
|
||||
}
|
||||
|
||||
for _, res := range r.failed {
|
||||
fmt.Println(res)
|
||||
}
|
||||
}
|
||||
|
||||
//IsFail : Overall result
|
||||
func (r *Report) IsFail() bool {
|
||||
return len(r.failed) > 0
|
||||
}
|
50
tests/apitests/api-testing/lib/system.go
Normal file
50
tests/apitests/api-testing/lib/system.go
Normal file
@ -0,0 +1,50 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/vmware/harbor/tests/apitests/api-testing/client"
|
||||
"github.com/vmware/harbor/tests/apitests/api-testing/models"
|
||||
)
|
||||
|
||||
//SystemUtil : For getting system info
|
||||
type SystemUtil struct {
|
||||
rootURI string
|
||||
hostname string
|
||||
testingClient *client.APIClient
|
||||
}
|
||||
|
||||
//NewSystemUtil : Constructor
|
||||
func NewSystemUtil(rootURI, hostname string, httpClient *client.APIClient) *SystemUtil {
|
||||
if len(strings.TrimSpace(rootURI)) == 0 || httpClient == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &SystemUtil{
|
||||
rootURI: rootURI,
|
||||
hostname: hostname,
|
||||
testingClient: httpClient,
|
||||
}
|
||||
}
|
||||
|
||||
//GetSystemInfo : Get systeminfo
|
||||
func (nsu *SystemUtil) GetSystemInfo() error {
|
||||
url := nsu.rootURI + "/api/systeminfo"
|
||||
data, err := nsu.testingClient.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var info models.SystemInfo
|
||||
if err := json.Unmarshal(data, &info); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if info.RegistryURL != nsu.hostname {
|
||||
return fmt.Errorf("Invalid registry url in system info: expect %s got %s ", nsu.hostname, info.RegistryURL)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
118
tests/apitests/api-testing/lib/user.go
Normal file
118
tests/apitests/api-testing/lib/user.go
Normal file
@ -0,0 +1,118 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/vmware/harbor/tests/apitests/api-testing/client"
|
||||
"github.com/vmware/harbor/tests/apitests/api-testing/models"
|
||||
)
|
||||
|
||||
//UserUtil : For user related
|
||||
type UserUtil struct {
|
||||
rootURI string
|
||||
testingClient *client.APIClient
|
||||
}
|
||||
|
||||
//NewUserUtil : Constructor
|
||||
func NewUserUtil(rootURI string, httpClient *client.APIClient) *UserUtil {
|
||||
if len(strings.TrimSpace(rootURI)) == 0 || httpClient == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &UserUtil{
|
||||
rootURI: rootURI,
|
||||
testingClient: httpClient,
|
||||
}
|
||||
}
|
||||
|
||||
//CreateUser : Create user
|
||||
func (uu *UserUtil) CreateUser(username, password string) error {
|
||||
if len(strings.TrimSpace(username)) == 0 ||
|
||||
len(strings.TrimSpace(password)) == 0 {
|
||||
return errors.New("Username and password required for creating user")
|
||||
}
|
||||
|
||||
u := models.User{
|
||||
Username: username,
|
||||
Password: password,
|
||||
Email: username + "@vmware.com",
|
||||
RealName: username + "pks",
|
||||
Comment: "testing",
|
||||
}
|
||||
|
||||
body, err := json.Marshal(&u)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s%s", uu.rootURI, "/api/users")
|
||||
if err := uu.testingClient.Post(url, body); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//DeleteUser : Delete testing account
|
||||
func (uu *UserUtil) DeleteUser(username string) error {
|
||||
uid := uu.GetUserID(username)
|
||||
if uid == -1 {
|
||||
return fmt.Errorf("Failed to get user with name %s", username)
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s%s%d", uu.rootURI, "/api/users/", uid)
|
||||
if err := uu.testingClient.Delete(url); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//GetUsers : Get users
|
||||
//If name specified, then return that one
|
||||
func (uu *UserUtil) GetUsers(name string) ([]models.ExistingUser, error) {
|
||||
url := fmt.Sprintf("%s%s", uu.rootURI, "/api/users")
|
||||
if len(strings.TrimSpace(name)) > 0 {
|
||||
url = url + "?username=" + name
|
||||
}
|
||||
|
||||
data, err := uu.testingClient.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var users []models.ExistingUser
|
||||
if err = json.Unmarshal(data, &users); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
//GetUserID : Get user ID
|
||||
//If user with the username is not existing, then return -1
|
||||
func (uu *UserUtil) GetUserID(username string) int {
|
||||
if len(strings.TrimSpace(username)) == 0 {
|
||||
return -1
|
||||
}
|
||||
|
||||
users, err := uu.GetUsers(username)
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
|
||||
if len(users) == 0 {
|
||||
return -1
|
||||
}
|
||||
|
||||
for _, u := range users {
|
||||
if u.Username == username {
|
||||
return u.ID
|
||||
}
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
10
tests/apitests/api-testing/models/endpoint.go
Normal file
10
tests/apitests/api-testing/models/endpoint.go
Normal file
@ -0,0 +1,10 @@
|
||||
package models
|
||||
|
||||
//Endpoint : For /api/targets
|
||||
type Endpoint struct {
|
||||
Endpoint string `json:"endpoint"`
|
||||
Name string `json:"name"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Type int `json:"type"`
|
||||
}
|
20
tests/apitests/api-testing/models/image.go
Normal file
20
tests/apitests/api-testing/models/image.go
Normal file
@ -0,0 +1,20 @@
|
||||
package models
|
||||
|
||||
//Repository : For /api/repositories
|
||||
type Repository struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
//Tag : For /api/repositories/:repo/tags
|
||||
type Tag struct {
|
||||
Digest string `json:"digest"`
|
||||
Name string `json:"name"`
|
||||
Signature map[string]interface{} `json:"signature, omitempty"`
|
||||
ScanOverview *ScanOverview `json:"scan_overview, omitempty"`
|
||||
}
|
||||
|
||||
//ScanOverview : For scanning
|
||||
type ScanOverview struct {
|
||||
Status string `json:"scan_status"`
|
||||
}
|
7
tests/apitests/api-testing/models/member.go
Normal file
7
tests/apitests/api-testing/models/member.go
Normal file
@ -0,0 +1,7 @@
|
||||
package models
|
||||
|
||||
//Member : For /api/projects/:pid/members
|
||||
type Member struct {
|
||||
UserName string `json:"username"`
|
||||
Roles []int `json:"roles"`
|
||||
}
|
18
tests/apitests/api-testing/models/project.go
Normal file
18
tests/apitests/api-testing/models/project.go
Normal file
@ -0,0 +1,18 @@
|
||||
package models
|
||||
|
||||
//Project : For /api/projects
|
||||
type Project struct {
|
||||
Name string `json:"project_name"`
|
||||
Metadata *Metadata `json:"metadata, omitempty"`
|
||||
}
|
||||
|
||||
//Metadata : Metadata for project
|
||||
type Metadata struct {
|
||||
AccessLevel string `json:"public"`
|
||||
}
|
||||
|
||||
//ExistingProject : For /api/projects?name=***
|
||||
type ExistingProject struct {
|
||||
Name string `json:"name"`
|
||||
ID int `json:"project_id"`
|
||||
}
|
10
tests/apitests/api-testing/models/replication.go
Normal file
10
tests/apitests/api-testing/models/replication.go
Normal file
@ -0,0 +1,10 @@
|
||||
package models
|
||||
|
||||
//ReplicationPolicy : For /api/replications
|
||||
type ReplicationPolicy struct {
|
||||
ProjectID int `json:"project_id"`
|
||||
|
||||
}
|
||||
|
||||
type ExistingReplicationPolicy struct {
|
||||
}
|
7
tests/apitests/api-testing/models/system_info.go
Normal file
7
tests/apitests/api-testing/models/system_info.go
Normal file
@ -0,0 +1,7 @@
|
||||
package models
|
||||
|
||||
//SystemInfo : For GET /api/systeminfo
|
||||
type SystemInfo struct {
|
||||
AuthMode string `json:"auth_mode"`
|
||||
RegistryURL string `json:"registry_url"`
|
||||
}
|
16
tests/apitests/api-testing/models/user.go
Normal file
16
tests/apitests/api-testing/models/user.go
Normal file
@ -0,0 +1,16 @@
|
||||
package models
|
||||
|
||||
//User : For /api/users
|
||||
type User struct {
|
||||
Username string `json:"username"`
|
||||
RealName string `json:"realname"`
|
||||
Password string `json:"password"`
|
||||
Email string `json:"email"`
|
||||
Comment string `json:"comment"`
|
||||
}
|
||||
|
||||
//ExistingUser : For GET /api/users
|
||||
type ExistingUser struct {
|
||||
User
|
||||
ID int `json:"user_id"`
|
||||
}
|
11
tests/apitests/api-testing/tests/suites/suite.go
Normal file
11
tests/apitests/api-testing/tests/suites/suite.go
Normal file
@ -0,0 +1,11 @@
|
||||
package suites
|
||||
|
||||
import (
|
||||
"github.com/vmware/harbor/tests/apitests/api-testing/envs"
|
||||
"github.com/vmware/harbor/tests/apitests/api-testing/lib"
|
||||
)
|
||||
|
||||
//Suite : Run a group of test cases
|
||||
type Suite interface {
|
||||
Run(onEnvironment envs.Environment) *lib.Report
|
||||
}
|
22
tests/apitests/api-testing/tests/suites/suite01/run_test.go
Normal file
22
tests/apitests/api-testing/tests/suites/suite01/run_test.go
Normal file
@ -0,0 +1,22 @@
|
||||
package suite01
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/vmware/harbor/tests/apitests/api-testing/envs"
|
||||
)
|
||||
|
||||
//TestRun : Start to run the case
|
||||
func TestRun(t *testing.T) {
|
||||
//Initialize env
|
||||
if err := envs.ConcourseCIEnv.Load(); err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
suite := ConcourseCiSuite01{}
|
||||
report := suite.Run(&envs.ConcourseCIEnv)
|
||||
report.Print()
|
||||
if report.IsFail() {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
177
tests/apitests/api-testing/tests/suites/suite01/suite.go
Normal file
177
tests/apitests/api-testing/tests/suites/suite01/suite.go
Normal file
@ -0,0 +1,177 @@
|
||||
package suite01
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/vmware/harbor/tests/apitests/api-testing/envs"
|
||||
"github.com/vmware/harbor/tests/apitests/api-testing/lib"
|
||||
)
|
||||
|
||||
//Steps of suite01:
|
||||
// s0: Get systeminfo
|
||||
// s1: create project
|
||||
// s2: create user "cody"
|
||||
// s3: assign cody as developer
|
||||
// s4: push a busybox image to project
|
||||
// s5: scan image
|
||||
// s6: pull image from project
|
||||
// s7: remove "cody" from project member list
|
||||
// s8: pull image from project [FAIL]
|
||||
// s9: remove repository busybox
|
||||
// s10: delete project
|
||||
// s11: delete user
|
||||
|
||||
//ConcourseCiSuite01 : For harbor journey in concourse pipeline
|
||||
type ConcourseCiSuite01 struct{}
|
||||
|
||||
//Run : Run a group of cases
|
||||
func (ccs *ConcourseCiSuite01) Run(onEnvironment *envs.Environment) *lib.Report {
|
||||
report := &lib.Report{}
|
||||
|
||||
//s0
|
||||
sys := lib.NewSystemUtil(onEnvironment.RootURI(), onEnvironment.Hostname, onEnvironment.HTTPClient)
|
||||
if err := sys.GetSystemInfo(); err != nil {
|
||||
report.Failed("GetSystemInfo", err)
|
||||
} else {
|
||||
report.Passed("GetSystemInfo")
|
||||
}
|
||||
|
||||
//s1
|
||||
pro := lib.NewProjectUtil(onEnvironment.RootURI(), onEnvironment.HTTPClient)
|
||||
if err := pro.CreateProject(onEnvironment.TestingProject, false); err != nil {
|
||||
report.Failed("CreateProject", err)
|
||||
} else {
|
||||
report.Passed("CreateProject")
|
||||
}
|
||||
|
||||
//s2
|
||||
usr := lib.NewUserUtil(onEnvironment.RootURI(), onEnvironment.HTTPClient)
|
||||
if err := usr.CreateUser(onEnvironment.Account, onEnvironment.Password); err != nil {
|
||||
report.Failed("CreateUser", err)
|
||||
} else {
|
||||
report.Passed("CreateUser")
|
||||
}
|
||||
|
||||
//s3
|
||||
if err := pro.AssignRole(onEnvironment.TestingProject, onEnvironment.Account); err != nil {
|
||||
report.Failed("AssignRole", err)
|
||||
} else {
|
||||
report.Passed("AssignRole")
|
||||
}
|
||||
|
||||
//s4
|
||||
if err := ccs.pushImage(onEnvironment); err != nil {
|
||||
report.Failed("pushImage", err)
|
||||
} else {
|
||||
report.Passed("pushImage")
|
||||
}
|
||||
|
||||
//s5
|
||||
img := lib.NewImageUtil(onEnvironment.RootURI(), onEnvironment.HTTPClient)
|
||||
repoName := fmt.Sprintf("%s/%s", onEnvironment.TestingProject, onEnvironment.ImageName)
|
||||
if err := img.ScanTag(repoName, onEnvironment.ImageTag); err != nil {
|
||||
report.Failed("ScanTag", err)
|
||||
} else {
|
||||
report.Passed("ScanTag")
|
||||
}
|
||||
|
||||
//s6
|
||||
if err := ccs.pullImage(onEnvironment); err != nil {
|
||||
report.Failed("pullImage[1]", err)
|
||||
} else {
|
||||
report.Passed("pullImage[1]")
|
||||
}
|
||||
|
||||
//s7
|
||||
uid := usr.GetUserID(onEnvironment.Account)
|
||||
if err := pro.RevokeRole(onEnvironment.TestingProject, uid); err != nil {
|
||||
report.Failed("RevokeRole", err)
|
||||
} else {
|
||||
report.Passed("RevokeRole")
|
||||
}
|
||||
|
||||
//s8
|
||||
if err := ccs.pullImage(onEnvironment); err == nil {
|
||||
report.Failed("pullImage[2]", err)
|
||||
} else {
|
||||
report.Passed("pullImage[2]")
|
||||
}
|
||||
|
||||
//s9
|
||||
if err := img.DeleteRepo(repoName); err != nil {
|
||||
report.Failed("DeleteRepo", err)
|
||||
} else {
|
||||
report.Passed("DeleteRepo")
|
||||
}
|
||||
|
||||
//s10
|
||||
if err := pro.DeleteProject(onEnvironment.TestingProject); err != nil {
|
||||
report.Failed("DeleteProject", err)
|
||||
} else {
|
||||
report.Passed("DeleteProject")
|
||||
}
|
||||
|
||||
//s11
|
||||
if err := usr.DeleteUser(onEnvironment.Account); err != nil {
|
||||
report.Failed("DeleteUser", err)
|
||||
} else {
|
||||
report.Passed("DeleteUser")
|
||||
}
|
||||
|
||||
return report
|
||||
}
|
||||
|
||||
func (ccs *ConcourseCiSuite01) pushImage(onEnvironment *envs.Environment) error {
|
||||
docker := onEnvironment.DockerClient
|
||||
if err := docker.Status(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
imagePulling := fmt.Sprintf("%s:%s", onEnvironment.ImageName, onEnvironment.ImageTag)
|
||||
if err := docker.Pull(imagePulling); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := docker.Login(onEnvironment.Account, onEnvironment.Password, onEnvironment.Hostname); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
imagePushing := fmt.Sprintf("%s/%s/%s:%s",
|
||||
onEnvironment.Hostname,
|
||||
onEnvironment.TestingProject,
|
||||
onEnvironment.ImageName,
|
||||
onEnvironment.ImageTag)
|
||||
|
||||
if err := docker.Tag(imagePulling, imagePushing); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := docker.Push(imagePushing); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ccs *ConcourseCiSuite01) pullImage(onEnvironment *envs.Environment) error {
|
||||
docker := onEnvironment.DockerClient
|
||||
if err := docker.Status(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := docker.Login(onEnvironment.Account, onEnvironment.Password, onEnvironment.Hostname); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
imagePulling := fmt.Sprintf("%s/%s/%s:%s",
|
||||
onEnvironment.Hostname,
|
||||
onEnvironment.TestingProject,
|
||||
onEnvironment.ImageName,
|
||||
onEnvironment.ImageTag)
|
||||
|
||||
if err := docker.Pull(imagePulling); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Loading…
Reference in New Issue
Block a user