设为首页 收藏本站
查看: 1284|回复: 0

[经验分享] docker命令之login

[复制链接]
累计签到:8 天
连续签到:1 天
发表于 2015-10-13 11:16:13 | 显示全部楼层 |阅读模式
  docker version:1.0.0
  位置:login命令的源码位于/docker/api/client/command.go中
  使用方式:docker login [-e|-email=""] [-p|--password=""] [-u|--username=""] [SERVER]
  功能:用于登录registry
  eg. docker login 127.0.0.1:5000
  =================
  以下是在client端执行login的过程,通过api的CmdLogin接口,组建POST /auth,将此请求发送给docker deamon的http server
  http server将此请求路由到postAuth(api/server/server.go),创建一个名为auth的job。
   DSC0000.jpg

   DSC0001.jpg

  

  ================================
  ------------------------dcoker client组建POST auth请求----------------------------
  func (cli *DockerCli) CmdLogin(args ...string) error {
cmd := cli.Subcmd("login", "[SERVER]", "Register or log in to a Docker registry server, if no server is specified \""+registry.IndexServerAddress()+"\" is the default.")

var username, password, email string

cmd.StringVar(&username, []string{"u", "-username"}, "", "Username")
cmd.StringVar(&password, []string{"p", "-password"}, "", "Password")
cmd.StringVar(&email, []string{"e", "-email"}, "", "Email")
//分析输入的参数

  err := cmd.Parse(args)
if err != nil {
return nil
}

  //如果命令行中没有registry地址,那么获取默认的registry index server的地址:const INDEXSERVER = "https://index.docker.io/v1/"
  //如果有将使用命令行中的registry地址
serverAddress := registry.IndexServerAddress()
if len(cmd.Args()) > 0 {
serverAddress = cmd.Arg(0)
}

//提示函数
promptDefault := func(prompt string, configDefault string) {
if configDefault == "" {
fmt.Fprintf(cli.out, "%s: ", prompt)
} else {
fmt.Fprintf(cli.out, "%s (%s): ", prompt, configDefault)
}
}

// 读取输入
readInput := func(in io.Reader, out io.Writer) string {
reader := bufio.NewReader(in)
line, _, err := reader.ReadLine()
if err != nil {
fmt.Fprintln(out, err.Error())
os.Exit(1)
}
return string(line)
}

//获取配置文件
cli.LoadConfigFile()   //获取“HOME”目录下的registry授权信息文件.dockercfg      const CONFIGFILE = ".dockercfg"

  //在授权配置文件中没有找到registry项,新建此registry的配置项
authconfig, ok := cli.configFile.Configs[serverAddress]
if !ok {
authconfig = registry.AuthConfig{}
}

//填充username
if username == "" {
promptDefault("Username", authconfig.Username)
username = readInput(cli.in, cli.out)
if username == "" {
username = authconfig.Username
}
}

  //填充passwd和email
if username != authconfig.Username {
if password == "" {
oldState, _ := term.SaveState(cli.terminalFd)
fmt.Fprintf(cli.out, "Password: ")
term.DisableEcho(cli.terminalFd, oldState)

password = readInput(cli.in, cli.out)
fmt.Fprint(cli.out, "\n")

term.RestoreTerminal(cli.terminalFd, oldState)
if password == "" {
return fmt.Errorf("Error : Password Required")
}
}

if email == "" {
promptDefault("Email", authconfig.Email)
email = readInput(cli.in, cli.out)
if email == "" {
email = authconfig.Email
}
}
} else {
password = authconfig.Password
email = authconfig.Email
}

  //将新的授权信息添加授权结构体中
authconfig.Username = username
authconfig.Password = password
authconfig.Email = email
authconfig.ServerAddress = serverAddress
cli.configFile.Configs[serverAddress] = authconfig

//向registry发起POST /auth请求,在registry的index中进行授权方面的认证过程
stream, statusCode, err := cli.call("POST", "/auth", cli.configFile.Configs[serverAddress], false)
if statusCode == 401 {
delete(cli.configFile.Configs, serverAddress)
registry.SaveConfig(cli.configFile)
return err
}
if err != nil {
return err
}
var out2 engine.Env
err = out2.Decode(stream)
if err != nil {
cli.configFile, _ = registry.LoadConfig(os.Getenv("HOME"))
return err
}

  //请求成功,保存授权信息到.dockercfg中
registry.SaveConfig(cli.configFile)
if out2.Get("Status") != "" {
fmt.Fprintf(cli.out, "%s\n", out2.Get("Status"))
}
return nil
}


  ----------------------------------向docker deamon的http server发起POST /auth请求---------------------------------------------------------------
  位置:cil.call位于/api/client/utils.go
  

  stream, statusCode, err := cli.call("POST", "/auth", cli.configFile.Configs[serverAddress], false)

  func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo bool) (io.ReadCloser, int, error) {
  //解析registry地址
params, err := cli.encodeData(data)
if err != nil {
return nil, -1, err
}

  //新建方法为POST路径为/auth的http请求
req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), params)
if err != nil {
return nil, -1, err
}

  //若registry地址存在,获取index地址信息
if passAuthInfo {
cli.LoadConfigFile()
// Resolve the Auth config relevant for this server
authConfig := cli.configFile.ResolveAuthConfig(registry.IndexServerAddress()) //获取index地址信息 const INDEXSERVER = "https://index.docker.io/v1/"

  //组装http请求的头部
getHeaders := func(authConfig registry.AuthConfig) (map[string][]string, error) {
buf, err := json.Marshal(authConfig)
if err != nil {
return nil, err
}
registryAuthHeader := []string{
base64.URLEncoding.EncodeToString(buf),
}
return map[string][]string{"X-Registry-Auth": registryAuthHeader}, nil
}

  
if headers, err := getHeaders(authConfig); err == nil && headers != nil {
for k, v := range headers {
req.Header[k] = v
}
}
}
req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION)
req.URL.Host = cli.addr
req.URL.Scheme = cli.scheme
if data != nil {
req.Header.Set("Content-Type", "application/json")
} else if method == "POST" {
req.Header.Set("Content-Type", "plain/text")
}

  //发起POST /auth请求, 在真正发起请求钱,做了个ping的动作
resp, err := cli.HTTPClient().Do(req)
if err != nil {
if strings.Contains(err.Error(), "connection refused") {
return nil, -1, ErrConnectionRefused
}
return nil, -1, err
}
//请求返回错误码处理
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, -1, err
}
if len(body) == 0 {
return nil, resp.StatusCode, fmt.Errorf(&quot;Error: request returned %s for API route and version %s, check if the server supports the requested API version&quot;, http.StatusText(resp.StatusCode), req.URL)
}
return nil, resp.StatusCode, fmt.Errorf(&quot;Error response from daemon: %s&quot;, bytes.TrimSpace(body))
}
//返回index服务响应的包体和状态码
return resp.Body, resp.StatusCode, nil
}

  ------------------------------------docker deamon的http server处理client端的POST auth请求---------------------------
  代码位置 api/server/server.go
  func postAuth(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  //获取认证配置,新建名为auth的job
var (
authConfig, err = ioutil.ReadAll(r.Body)
job = eng.Job(&quot;auth&quot;)
stdoutBuffer = bytes.NewBuffer(nil)
)
if err != nil {
return err
}
job.Setenv(&quot;authConfig&quot;, string(authConfig))
job.Stdout.Add(stdoutBuffer)
  //运行auth job
if err = job.Run(); err != nil {
return err
}
if status := engine.Tail(stdoutBuffer, 1); status != &quot;&quot; {
var env engine.Env
env.Set(&quot;Status&quot;, status)
return writeJSON(w, http.StatusOK, env)
}
w.WriteHeader(http.StatusNoContent)
return nil
}

  --------------------------------------auth job调用registry包中的auth.go中的Login方法向registry发起用户认证-------------------------
  registry包中向引擎eng注册的handler,使用s.Auth处理。
  //代码位置registry/service.go
  // Install installs registry capabilities to eng.
func (s *Service) Install(eng *engine.Engine) error {
eng.Register(&quot;auth&quot;, s.Auth)
eng.Register(&quot;search&quot;, s.Search)
return nil
}


  func (s *Service) Auth(job *engine.Job) engine.Status {
var (
err error
authConfig = &AuthConfig{}
)

job.GetenvJson(&quot;authConfig&quot;, authConfig)
// TODO: this is only done here because auth and registry need to be merged into one pkg
if addr := authConfig.ServerAddress; addr != &quot;&quot; && addr != IndexServerAddress() {
addr, err = ExpandAndVerifyRegistryUrl(addr)
if err != nil {
return job.Error(err)
}
authConfig.ServerAddress = addr
}
//使用registry/auth.go中的Login处理
  status, err := Login(authConfig, HTTPRequestFactory(nil))
if err != nil {
return job.Error(err)
}
job.Printf(&quot;%s\n&quot;, status)
return engine.StatusOK
}

  ----------------------registry/auth.go---------------------
  docker客户端向docker-registry发起认证请求使用POST方法 /v1/users路由,
  docker-registry收到请求后,进行注册,若注册成功返回给docker客户端201,并提示激活用户
  若返回400,则用户已经注册
  

  // try to register/login to the registry server
func Login(authConfig *AuthConfig, factory *utils.HTTPRequestFactory) (string, error) {
var (
status string
reqBody []byte
err error
client = &http.Client{
Transport: &http.Transport{
DisableKeepAlives: true,
Proxy: http.ProxyFromEnvironment,
},
CheckRedirect: AddRequiredHeadersToRedirectedRequests,
}
reqStatusCode = 0
serverAddress = authConfig.ServerAddress //使用命令中提供的registry地址
)

if serverAddress == &quot;&quot; {
serverAddress = IndexServerAddress() //如果命令行中没有提供registry地址,使用官方的registry地址
}

loginAgainstOfficialIndex := serverAddress == IndexServerAddress()

// to avoid sending the server address to the server it should be removed before being marshalled
authCopy := *authConfig
authCopy.ServerAddress = &quot;&quot;

jsonBody, err := json.Marshal(authCopy)
if err != nil {
return &quot;&quot;, fmt.Errorf(&quot;Config Error: %s&quot;, err)
}

// using `bytes.NewReader(jsonBody)` here causes the server to respond with a 411 status.
b := strings.NewReader(string(jsonBody))
req1, err := http.Post(serverAddress&#43;&quot;users/&quot;, &quot;application/json; charset=utf-8&quot;, b)//向registry发起POST方法,路由为/v1/users的请求
if err != nil {
return &quot;&quot;, fmt.Errorf(&quot;Server Error: %s&quot;, err)
}
reqStatusCode = req1.StatusCode
defer req1.Body.Close()
reqBody, err = ioutil.ReadAll(req1.Body)
if err != nil {
return &quot;&quot;, fmt.Errorf(&quot;Server Error: [%#v] %s&quot;, reqStatusCode, err)
}
//下面为分析registry返回的状态码的情况
if reqStatusCode == 201 {//收到201状态码,代码用户注册成功,提示用户激活,退出,不做登录操作
if loginAgainstOfficialIndex { //如果是docker官方的registry,提示信息
status = &quot;Account created. Please use the confirmation link we sent&quot; &#43;
&quot; to your e-mail to activate it.&quot;
} else { //如果是私有的registry,提示信息
status = &quot;Account created. Please see the documentation of the registry &quot; &#43; serverAddress &#43; &quot; for instructions how to activate it.&quot;
}
} else if reqStatusCode == 400 { //收到400,代表用户已存在,发起GET方法,路由为/v1/users的请求,进行用户登录(这个也许是docker官方的registry的状态码,私有的返回401)
if string(reqBody) == &quot;\&quot;Username or email already exists\&quot;&quot; {
req, err := factory.NewRequest(&quot;GET&quot;, serverAddress&#43;&quot;users/&quot;, nil)
req.SetBasicAuth(authConfig.Username, authConfig.Password)
resp, err := client.Do(req)
if err != nil {
return &quot;&quot;, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return &quot;&quot;, err
}
  //发起登录请求后,若收到200,代表用户登录成功
if resp.StatusCode == 200 {
status = &quot;Login Succeeded&quot;
} else if resp.StatusCode == 401 { //发起登录请求后,收到401,代表用户名或密码错误
return &quot;&quot;, fmt.Errorf(&quot;Wrong login/password, please try again&quot;)
} else if resp.StatusCode == 403 { //发起登录请求后,收到403,代表账户没有激活
if loginAgainstOfficialIndex { //如果是docker官方的registry,提示信息
return &quot;&quot;, fmt.Errorf(&quot;Login: Account is not Active. Please check your e-mail for a confirmation link.&quot;)
}
return &quot;&quot;, fmt.Errorf(&quot;Login: Account is not Active. Please see the documentation of the registry %s for instructions how to activate it.&quot;, serverAddress)
} else { //收到其他状态码的提示信息
return &quot;&quot;, fmt.Errorf(&quot;Login: %s (Code: %d; Headers: %s)&quot;, body, resp.StatusCode, resp.Header)
}
} else { //注册时收到其他状态信息时
return &quot;&quot;, fmt.Errorf(&quot;Registration: %s&quot;, reqBody)
}
} else if reqStatusCode == 401 {//注册时收到401,代表登录私有registry的用户登录
// This case would happen with private registries where /v1/users is
// protected, so people can use `docker login` as an auth check.
req, err := factory.NewRequest(&quot;GET&quot;, serverAddress&#43;&quot;users/&quot;, nil)
  
req.SetBasicAuth(authConfig.Username, authConfig.Password)
resp, err := client.Do(req)
if err != nil {
return &quot;&quot;, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return &quot;&quot;, err
}
if resp.StatusCode == 200 { //登录私有registry,若收到200,代表登录成功
status = &quot;Login Succeeded&quot;
} else if resp.StatusCode == 401 {//登录私有registry,若收到401,代表用户名或密码错误
return &quot;&quot;, fmt.Errorf(&quot;Wrong login/password, please try again&quot;)
} else {
return &quot;&quot;, fmt.Errorf(&quot;Login: %s (Code: %d; Headers: %s)&quot;, body,
resp.StatusCode, resp.Header)
}
} else {
return &quot;&quot;, fmt.Errorf(&quot;Unexpected status code [%d] : %s&quot;, reqStatusCode, reqBody)
}
return status, nil
}

  registry使用standalone方式,从docker client访问registry协议流程图
DSC0002.jpg

  1.client向registry发起ping操作,使用GET方法,协议版本为V1,试探是否能够和registry通信
  包体内容为
DSC0003.jpg
  2.registry向client返回200 OK,代码已经收到client的ping操作请求
  包体为
DSC0004.jpg
  3.client将登录的用户信息交给registry端,适应POST方法代码要求registry创建此用户,若此用户存在,则返回401
DSC0005.jpg
  4.registry返回401错误码,代表此用户已经存在
DSC0006.jpg
  5.client收到401,知道了registry端已有此用户,client使用GET方法,携带basic authorization验证用户信息
DSC0007.jpg
  6、registry给client返回200 OK,代表此用户通过验证

         版权声明:本文为博主原创文章,未经博主允许不得转载。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-126218-1-1.html 上篇帖子: docker 私有hub搭建及使用 下篇帖子: Docker 使用方法总结之:容器的数据卷操作
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表