Harbor 源码分析之API(四)

首先看一下harbor的API,harbor代码结构还是比较简单,如果熟悉springmvc的人对此可能更加容易理解。它整个web服务是基于beego的,首先看路由src/ui/router.go。

beego.SetStaticPath("/static", "./static")
    beego.SetStaticPath("/i18n", "./static/i18n")

    //Page Controllers:
    beego.Router("/", &controllers.IndexController{})
    beego.Router("/sign-in", &controllers.IndexController{})
    beego.Router("/sign-up", &controllers.IndexController{})
    beego.Router("/reset_password", &controllers.IndexController{})

    beego.Router("/harbor", &controllers.IndexController{})

    beego.Router("/harbor/sign-in", &controllers.IndexController{})
    beego.Router("/harbor/sign-up", &controllers.IndexController{})
    beego.Router("/harbor/dashboard", &controllers.IndexController{})
    beego.Router("/harbor/projects", &controllers.IndexController{})
    beego.Router("/harbor/projects/:id/repository", &controllers.IndexController{})
    beego.Router("/harbor/projects/:id/replication", &controllers.IndexController{})
    beego.Router("/harbor/projects/:id/member", &controllers.IndexController{})
    beego.Router("/harbor/projects/:id/log", &controllers.IndexController{})
    beego.Router("/harbor/tags/:id/*", &controllers.IndexController{})

    beego.Router("/harbor/users", &controllers.IndexController{})
    beego.Router("/harbor/logs", &controllers.IndexController{})
    beego.Router("/harbor/replications", &controllers.IndexController{})
    beego.Router("/harbor/replications/endpoints", &controllers.IndexController{})
    beego.Router("/harbor/replications/rules", &controllers.IndexController{})
    beego.Router("/harbor/tags", &controllers.IndexController{})
    beego.Router("/harbor/configs", &controllers.IndexController{})

    beego.Router("/login", &controllers.CommonController{}, "post:Login")
    beego.Router("/log_out", &controllers.CommonController{}, "get:LogOut")
    beego.Router("/reset", &controllers.CommonController{}, "post:ResetPassword")
    beego.Router("/userExists", &controllers.CommonController{}, "post:UserExists")
    beego.Router("/sendEmail", &controllers.CommonController{}, "get:SendEmail")

    //API:
    beego.Router("/api/search", &api.SearchAPI{})
    beego.Router("/api/projects/:pid([0-9]+)/members/?:mid", &api.ProjectMemberAPI{})
    beego.Router("/api/projects/", &api.ProjectAPI{}, "get:List;post:Post;head:Head")
    beego.Router("/api/projects/:id([0-9]+)", &api.ProjectAPI{})
    beego.Router("/api/projects/:id([0-9]+)/publicity", &api.ProjectAPI{}, "put:ToggleProjectPublic")
    beego.Router("/api/projects/:id([0-9]+)/logs/filter", &api.ProjectAPI{}, "post:FilterAccessLog")
    beego.Router("/api/statistics", &api.StatisticAPI{})
    beego.Router("/api/users/?:id", &api.UserAPI{})
    beego.Router("/api/users/:id([0-9]+)/password", &api.UserAPI{}, "put:ChangePassword")
    beego.Router("/api/internal/syncregistry", &api.InternalAPI{}, "post:SyncRegistry")
    beego.Router("/api/repositories", &api.RepositoryAPI{})
    beego.Router("/api/repositories/*/tags/?:tag", &api.RepositoryAPI{}, "delete:Delete")
    beego.Router("/api/repositories/*/tags", &api.RepositoryAPI{}, "get:GetTags")
    beego.Router("/api/repositories/*/tags/:tag/manifest", &api.RepositoryAPI{}, "get:GetManifests")
    beego.Router("/api/repositories/*/signatures", &api.RepositoryAPI{}, "get:GetSignatures")
    beego.Router("/api/jobs/replication/", &api.RepJobAPI{}, "get:List")
    beego.Router("/api/jobs/replication/:id([0-9]+)", &api.RepJobAPI{})
    beego.Router("/api/jobs/replication/:id([0-9]+)/log", &api.RepJobAPI{}, "get:GetLog")
    beego.Router("/api/policies/replication/:id([0-9]+)", &api.RepPolicyAPI{})
    beego.Router("/api/policies/replication", &api.RepPolicyAPI{}, "get:List")
    beego.Router("/api/policies/replication", &api.RepPolicyAPI{}, "post:Post")
    beego.Router("/api/policies/replication/:id([0-9]+)/enablement", &api.RepPolicyAPI{}, "put:UpdateEnablement")
    beego.Router("/api/targets/", &api.TargetAPI{}, "get:List")
    beego.Router("/api/targets/", &api.TargetAPI{}, "post:Post")
    beego.Router("/api/targets/:id([0-9]+)", &api.TargetAPI{})
    beego.Router("/api/targets/:id([0-9]+)/policies/", &api.TargetAPI{}, "get:ListPolicies")
    beego.Router("/api/targets/ping", &api.TargetAPI{}, "post:Ping")
    beego.Router("/api/targets/:id([0-9]+)/ping", &api.TargetAPI{}, "post:PingByID")
    beego.Router("/api/users/:id/sysadmin", &api.UserAPI{}, "put:ToggleUserAdminRole")
    beego.Router("/api/repositories/top", &api.RepositoryAPI{}, "get:GetTopRepos")
    beego.Router("/api/logs", &api.LogAPI{})
    beego.Router("/api/configurations", &api.ConfigAPI{})
    beego.Router("/api/configurations/reset", &api.ConfigAPI{}, "post:Reset")

    beego.Router("/api/systeminfo", &api.SystemInfoAPI{}, "get:GetGeneralInfo")
    beego.Router("/api/systeminfo/volumes", &api.SystemInfoAPI{}, "get:GetVolumeInfo")
    beego.Router("/api/systeminfo/getcert", &api.SystemInfoAPI{}, "get:GetCert")
    beego.Router("/api/ldap/ping", &api.LdapAPI{}, "post:Ping")
    beego.Router("/api/ldap/users/search", &api.LdapAPI{}, "post:Search")
    beego.Router("/api/ldap/users/import", &api.LdapAPI{}, "post:ImportUser")
    beego.Router("/api/email/ping", &api.EmailAPI{}, "post:Ping")

    //external service that hosted on harbor process:
    beego.Router("/service/notifications", &service.NotificationHandler{})
    beego.Router("/service/token", &token.Handler{})

    //Error pages
    beego.ErrorController(&controllers.ErrorController{})

简单介绍一下主要的接口,第一个介绍登录接口,post:Login。

func (cc *CommonController) Login() {
    principal := cc.GetString("principal")
    password := cc.GetString("password")

    user, err := auth.Login(models.AuthModel{
        Principal: principal,
        Password:  password,
    })
    if err != nil {
        log.Errorf("Error occurred in UserLogin: %v", err)
        cc.CustomAbort(http.StatusUnauthorized, "")
    }

    if user == nil {
        cc.CustomAbort(http.StatusUnauthorized, "")
    }

    cc.SetSession("userId", user.UserID)
    cc.SetSession("username", user.Username)
}

登录的接口,获取用户名和密码,验证src/ui/auth/authenticator.go,

func Login(m models.AuthModel) (*models.User, error) {

    authMode, err := config.AuthMode()
    if err != nil {
        return nil, err
    }
    if authMode == "" || m.Principal == "admin" {
        authMode = "db_auth"
    }
    log.Debug("Current AUTH_MODE is ", authMode)

    authenticator, ok := registry[authMode]
    if !ok {
        return nil, fmt.Errorf("Unrecognized auth_mode: %s", authMode)
    }
    if lock.IsLocked(m.Principal) {
        log.Debugf("%s is locked due to login failure, login failed", m.Principal)
        return nil, nil
    }
    user, err := authenticator.Authenticate(m)
    if user == nil && err == nil {
        log.Debugf("Login failed, locking %s, and sleep for %v", m.Principal, frozenTime)
        lock.Lock(m.Principal)
        time.Sleep(frozenTime)
    }
    return user, err
}

authenticator.Authenticate验证,如果这里是db数据库验证的话,就在src/ui/auth/db/db.go

func (d *Auth) Authenticate(m models.AuthModel) (*models.User, error) {
    u, err := dao.LoginByDb(m)
    if err != nil {
        return nil, err
    }
    return u, nil
}

进入LoginByDb

func LoginByDb(auth models.AuthModel) (*models.User, error) {
    o := GetOrmer()

    var users []models.User
    n, err := o.Raw(`select * from user where (username = ? or email = ?) and deleted = 0`,
        auth.Principal, auth.Principal).QueryRows(&users)
    if err != nil {
        return nil, err
    }
    if n == 0 {
        return nil, nil
    }

    user := users[0]

    if user.Password != utils.Encrypt(auth.Password, user.Salt) {
        return nil, nil
    }

    user.Password = "" //do not return the password

    return &user, nil
}

很简单就是获取用户,验证密码,由于密码是加密存储的,所以是比较加密后的,auth.Password是明文密码,user.Password是加密过后的密码。当验证完用户的后吧用户的id和name报错到session里面,这个在后面会用到。还举个例子就是用户操作的API,包括用的curd等操作,
获取用户

func (ua *UserAPI) Get() {
.  .  .
        userList, err := dao.ListUsers(userQuery)
.  .  .     
}

还有修改用户

func (ua *UserAPI) Put() {
    .   .  .
    user := models.User{UserID: ua.userID}

    userQuery := models.User{UserID: ua.userID}
    u, err := dao.GetUser(userQuery)

    if err := dao.ChangeUserProfile(user); err != nil {
        .   .   .
}

创建用户

func (ua *UserAPI) Post() {

    .  .  . 
    userExist, err := dao.UserExists(user, "username")

    emailExist, err := dao.UserExists(user, "email")

    userID, err := dao.Register(user)
    .  .  .
}

其它的API操作类似,在此不再一一讲述。
harbor是镜像仓库,所以还有关于镜像的接口还是要说的,主要分为两个部分,一个是镜像仓库。一个是job。
镜像仓库主要也是一些CURD。先看src/ui/api/repository.go这个里面有查询和删除的方法,

func (ra *RepositoryAPI) Get() {
    projectID, err := ra.GetInt64("project_id")
    if err != nil || projectID <= 0 {
        ra.CustomAbort(http.StatusBadRequest, "invalid project_id")
    }

    project, err := dao.GetProjectByID(projectID)
    if err != nil {
        log.Errorf("failed to get project %d: %v", projectID, err)
        ra.CustomAbort(http.StatusInternalServerError, "")
    }

    if project == nil {
        ra.CustomAbort(http.StatusNotFound, fmt.Sprintf("project %d not found", projectID))
    }

    if project.Public == 0 {
        var userID int

        if svc_utils.VerifySecret(ra.Ctx.Request, config.JobserviceSecret()) {
            userID = 1
        } else {
            userID = ra.ValidateUser()
        }

        if !checkProjectPermission(userID, projectID) {
            ra.CustomAbort(http.StatusForbidden, "")
        }
    }

    keyword := ra.GetString("q")

    total, err := dao.GetTotalOfRepositoriesByProject(projectID, keyword)
    if err != nil {
        log.Errorf("failed to get total of repositories of project %d: %v", projectID, err)
        ra.CustomAbort(http.StatusInternalServerError, "")
    }

    page, pageSize := ra.GetPaginationParams()

    detail := ra.GetString("detail") == "1" || ra.GetString("detail") == "true"

    repositories, err := getRepositories(projectID,
        keyword, pageSize, pageSize*(page-1), detail)
    if err != nil {
        log.Errorf("failed to get repository: %v", err)
        ra.CustomAbort(http.StatusInternalServerError, "")
    }

    ra.SetPaginationHeader(total, page, pageSize)
    ra.Data["json"] = repositories
    ra.ServeJSON()
}

查询并支持分页返回,查询只是操作数据库,但删除要麻烦一些,应为删除需要调用后端docker registry接口删除,代码如下

func (r *Repository) DeleteTag(tag string) error {
    digest, exist, err := r.ManifestExist(tag)
    if err != nil {
        return err
    }

    if !exist {
        return &registry_error.Error{
            StatusCode: http.StatusNotFound,
        }
    }

    return r.DeleteManifest(digest)
}

深入看一下DeleteManifest

func (r *Repository) DeleteManifest(digest string) error {
    req, err := http.NewRequest("DELETE", buildManifestURL(r.Endpoint.String(), r.Name, digest), nil)
    if err != nil {
        return err
    }

    resp, err := r.client.Do(req)
    if err != nil {
        return parseError(err)
    }

    defer resp.Body.Close()

    if resp.StatusCode == http.StatusAccepted {
        return nil
    }

    b, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return err
    }

    return &registry_error.Error{
        StatusCode: resp.StatusCode,
        Detail:     string(b),
    }
}

就是调用registry去删除,如果大家对registry的api感兴趣可以参考Docker Registry HTTP API V2这个文档。
但是大家细心就会发现没有创建的接口,因为这个创建Repository是在上传镜像的时候创建的。这里又回到registry的配置文件了,

notifications:
  endpoints:
      - name: harbor
        disabled: false
        url: http://ui/service/notifications
        timeout: 3000ms
        threshold: 5
        backoff: 1s

这里仓库回调web hook创建Repository。

func (n *NotificationHandler) Post() {

    for _, event := range events {
    .  .   .
        if action == "push" {
            go func() {
                exist := dao.RepositoryExists(repository)
                if exist {
                    return
                }
                log.Debugf("Add repository %s into DB.", repository)
                repoRecord := models.RepoRecord{Name: repository, OwnerName: user, ProjectName: project}
                if err := dao.AddRepository(repoRecord); err != nil {
                    log.Errorf("Error happens when adding repository: %v", err)
                }
            }()
            go api.TriggerReplicationByRepository(repository, []string{tag}, models.RepOpTransfer)
        }
        if action == "pull" {
            go func() {
                log.Debugf("Increase the repository %s pull count.", repository)
                if err := dao.IncreasePullCount(repository); err != nil {
                    log.Errorf("Error happens when increasing pull count: %v", repository)
                }
            }()
        }
    }
}

这样push镜像到仓库的时候就可以收到回调了,首先判断repository是否存在如果不存在则创建,对于pull镜像操作通过IncreasePullCount更新数据库pull镜像次数。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柳清风09

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值