curl 调用jenkins的api

jenkins提供了rest api,通过调用接口,可以执行一些job的操作,如构建job ,新建job,启用禁用等操作骑车

其次curl是什么?

cURL是一个利用URL语法在命令行下工作的文件传输工具,1997年首次发行。它支持文件上传和下载,所以是综合传输工具,但按传统,习惯称cURL为下载工具。 cURL还包含了用于程序开发的libcurl。

cURL支持的通信协议有FTP、FTPS、HTTP、HTTPS、TFTP、SFTP、Gopher、SCP、Telnet、DICT、FILE、LDAP、LDAPS、IMAP、POP3、SMTP和RTSP。

curl还支持SSL认证、HTTP POST、HTTP PUT、FTP上传, HTTP form based upload、proxies、HTTP/2、cookies、用户名+密码认证(Basic, Plain, Digest, CRAM-MD5, NTLM, Negotiate and Kerberos)、file transfer resume、proxy tunneling。

可以看出curl支持http协议,所以调用jenkins的rest api是没有问题的。

接下来示例演示操作cur 对jenkins的job的启用和禁用及构建,示例中用到的jenkins url为:http://localhost:8080/jenkins/,job名称为:login,jenkins管理员账号:root, 密码:root

启用job login

curl --user root:root --data enable http://localhost:8080/jenkins/job/login/enable

禁用job login

curl --user root:root --data disable http://localhost:8080/jenkins/job/login/disable

构建job login

curl --user root:root --data build http://localhost:8080/jenkins/job/login/build

命令中–user 表示jenkins账号密码认证

执行结果如下(如果在windows下,是没有办法直接在cmd下执行curl命令,可以下git的客户端,在git bash中执行命令)

Administrator@OXPLAJ8T7PEPCDB MINGW64 ~
$ curl --user root:root  --data disable  http://localhost:8080/jenkins/job/login/disable
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100     7    0     0  100     7      0      7  0:00:01 --:--:--  0:00:01    56

Administrator@OXPLAJ8T7PEPCDB MINGW64 ~
$ curl --user root:root  --data enable  http://localhost:8080/jenkins/job/login/enable
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100     6    0     0  100     6      0      6  0:00:01 --:--:--  0:00:01    42

Administrator@OXPLAJ8T7PEPCDB MINGW64 ~
$ curl --user root:root  --data build http://localhost:8080/jenkins/job/login/build
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100     5    0     0  100     5      0      5  0:00:01 --:--:--  0:00:01    35

Administrator@OXPLAJ8T7PEPCDB MINGW64 ~
$

执行后在jenkins页面查看,可以看到禁用、停用、构建job

使用python3和flask构建RESTful API(接口测试服务)

引言

构建RESTful API貌似是开发的工作,和测试有和关系?

其实测试开发需要构建RESTful API的场景很多。比如测试Android应用,一般的接口测试只考虑了服务器端,至于客户端在网络异常或者服务端异常时如何反应,多数天朝的测试人员是没有考虑到的。客户端在对这些异常处理不够充分的时候,会出现崩溃等各种莫名其妙的问题。

为此一些走在前沿的测试人员会自己写一些RESTful API, 把服务端的域名劫持到自己的API,故意返回各种异常,看客户端的稳定性。

另外测试开发的测试工具需要和其他系统对接等场景也经常需要API。

术语

REST: REpresentational State Transfer

目标

  • GET – /api/Category – Retrieve all categories

  • POST – /api/Category – Add a new category

  • PUT – /api/Category – Update a category

  • DELETE – /api/Category – Delete a category

  • GET – /api/Comment – Retrieve all the stored comments

  • POST – /api/Comment – Add new comment

要求

  • python3.*
  • PostgreSQL

工程目录

project/
├── app.py
├── config.py
├── migrate.py
├── Model.py
├── requirements.txt
├── resources
│   └── Hello.py
│   └── Comment.py
│   └── Category.py
└── run.py

requirements.txt的内容如下:

flask
flask_restful
flask_script
flask_migrate
marshmallow
flask_sqlalchemy
flask_marshmallow
marshmallow-sqlalchemy
psycopg2
  • flask – Python的微框架

  • flask_restful – 这是Flask的扩展,可快速构建REST API。

  • flask_script – 提供了在Flask中编写外部脚本的支持。

  • flask_migrate – 使用Alembic的Flask应用进行SQLAlchemy数据库迁移。

  • marshmallow – ORM/ODM/框架无关的库,用于复杂数据类型(如对象)和Python数据类型转换。

  • flask_sqlalchemy – Flask扩展,增加了对SQLAlchemy的支持。

  • flask_marshmallow – 这是Flask和marshmallow的中间层。

  • marshmallow-sqlalchemy – 这是sqlalchemy和marshmallow的中间层。

  • psycopg – Python的PostgreSQL API。

安装依赖

# pip3 install -r requirements.txt

安装配置PostgreSQL

这里以 Ubuntu 16.04为例:

# sudo apt-get update && sudo apt-get upgrade
# apt-get install postgresql postgresql-contrib
# su - postgres
$ createdb api
$ createuser andrew --pwprompt #创建用户
$ psql -d api -c "ALTER USER andrew WITH PASSWORD 'api';"

参考资料:

https://linode.com/docs/databases/postgresql/how-to-install-postgresql-on-ubuntu-16-04/

https://www.digitalocean.com/community/tutorials/how-to-install-and-use-postgresql-on-ubuntu-14-04

配置

# -*- coding: utf-8 -*-
# Author:    xurongzhong#126.com wechat:pythontesting qq:37391319
# CreateDate: 2018-1-10

from flask import Blueprint
from flask_restful import Api
from resources.Hello import Hello
from resources.Category import CategoryResource
from resources.Comment import CommentResource


api_bp = Blueprint('api', __name__)
api = Api(api_bp)

# Routes
api.add_resource(Hello, '/Hello')
api.add_resource(CategoryResource, '/Category')
api.add_resource(CommentResource, '/Comment')

快速入门

app.py

from flask import Blueprint
from flask_restful import Api
from resources.Hello import Hello

api_bp = Blueprint('api', __name__)
api = Api(api_bp)

# Route
api.add_resource(Hello, '/Hello')

resource/Hello.py

#!/usr/bin/python
# -*- coding: utf-8 -*-
# Author:    xurongzhong#126.com wechat:pythontesting qq:37391319
# CreateDate: 2018-1-10

from flask_restful import Resource


class Hello(Resource):
    def get(self):
        return {"message": "Hello, World!"}

    def post(self):
        return {"message": "Hello, World!"}

run.py

from flask import Flask


def create_app(config_filename):
    app = Flask(__name__)
    app.config.from_object(config_filename)

    from app import api_bp
    app.register_blueprint(api_bp, url_prefix='/api')

    return app


if __name__ == "__main__":
    app = create_app("config")
    app.run(debug=True)

启动服务

$ python3 run.py
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 136-695-873

用浏览器访问: http://127.0.0.1:5000/api/Hello

{
    "hello": "world"
}

接入数据库

from flask import Flask
from marshmallow import Schema, fields, pre_load, validate
from flask_marshmallow import Marshmallow
from flask_sqlalchemy import SQLAlchemy


ma = Marshmallow()
db = SQLAlchemy()


class Comment(db.Model):
    __tablename__ = 'comments'
    id = db.Column(db.Integer, primary_key=True)
    comment = db.Column(db.String(250), nullable=False)
    creation_date = db.Column(db.TIMESTAMP, server_default=db.func.current_timestamp(), nullable=False)
    category_id = db.Column(db.Integer, db.ForeignKey('categories.id', ondelete='CASCADE'), nullable=False)
    category = db.relationship('Category', backref=db.backref('comments', lazy='dynamic' ))

    def __init__(self, comment, category_id):
        self.comment = comment
        self.category_id = category_id


class Category(db.Model):
    __tablename__ = 'categories'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(150), unique=True, nullable=False)

    def __init__(self, name):
        self.name = name


class CategorySchema(ma.Schema):
    id = fields.Integer()
    name = fields.String(required=True)


class CommentSchema(ma.Schema):
    id = fields.Integer(dump_only=True)
    category_id = fields.Integer(required=True)
    comment = fields.String(required=True, validate=validate.Length(1))
    creation_date = fields.DateTime()

migrate.py

from flask_script import Manager
from flask_migrate import Migrate, MigrateCommand
from Model import db
from run import create_app

app = create_app('config')

migrate = Migrate(app, db)
manager = Manager(app)
manager.add_command('db', MigrateCommand)


if __name__ == '__main__':
    manager.run()

数据迁移

$ python3 migrate.py db init
$ python3 migrate.py db migrate
$ python migrate.py db upgrade

修改Category.py 和Comment.py, 完整代码

测试

可以使用curl,比如:

curl http://127.0.0.1:5000/api/Category --data '{"name":"test5","id":5}' -H "Content-Type: application/json"

也可以在chrome中使用postman:

未分类

未分类

未分类

未分类

Openresty最佳案例 | 第4篇:OpenResty常见的api

获取请求参数

vim /usr/example/example.conf
 location /lua_var {
        default_type 'text/plain';
        content_by_lua_block {
         ngx.say(ngx.var.arg_a)
        }
   }

重新加载nginx配置文件: nginx -s reload

在浏览器上访问http://116.196.177.123/lua_var?a=323,浏览器显示:

323

在上述代码中,涉及到了2个api, 一是ngx.say(直接返回请求结果);二是ngx.var,它是获取请求的参数,比如本例子上的?a=323,获取之后,直接输出为请求结果。

获取请求类型

vim /usr/example/example.conf
  location /lua_request{
       default_type 'text/html';
       lua_code_cache off;
       content_by_lua_file  /usr/example/lua/lua_request.lua;
   }

vim /usr/example/lua/lua_request.lua ,添加一下代码:

local arg = ngx.req.get_uri_args()
for k,v in pairs(arg) do
   ngx.say("[GET ] key:", k, " v:", v)
end

ngx.req.read_body() -- 解析 body 参数之前一定要先读取 body
local arg = ngx.req.get_post_args()
for k,v in pairs(arg) do
   ngx.say("[POST] key:", k, " v:", v)
end

在上述例子中有以下的api:

  • ngx.req.get_uri_args 获取在uri上的get类型参数,返回的是一个table类型的数据结构。
  • ngx.req.read_body 读取body,这在解析body之前,一定要先读取body。
  • ngx.req.get_post_args 获取form表单类型的参数,返回结果是一个table类型的数据。
    使用curl模拟请求:
curl ‘http://116.196.177.123/lua_request?a=323&b=ss’ -d ‘c=12w&d=2se3’

返回的结果:

[GET ] key:b v:ss
[GET ] key:a v:323
[POST] key:d v:2se3
[POST] key:c v:12w

获取请求头

vim /usr/example/lua/lua_request.lua ,在原有的代码基础上,再添加一下代码:

local headers = ngx.req.get_headers()
ngx.say("headers begin", "<br/>")
ngx.say("Host : ", headers["Host"], "<br/>")
ngx.say("user-agent : ", headers["user-agent"], "<br/>")
ngx.say("user-agent : ", headers.user_agent, "<br/>")
for k,v in pairs(headers) do
    if type(v) == "table" then
        ngx.say(k, " : ", table.concat(v, ","), "<br/>")
    else
        ngx.say(k, " : ", v, "<br/>")
    end
end

重新加载nginx -s reload

使用curl模拟请求:

curl ‘http://116.196.177.123/lua_request?a=323&b=ss’ -d ‘c=12w&d=2se3’
[GET ] key:b v:ss
[GET ] key:a v:323
[POST] key:d v:2se3
[POST] key:c v:12w
headers begin<br/>
Host : 116.196.77.157<br/>
user-agent : curl/7.53.0<br/>
user-agent : curl/7.53.0<br/>
host : 116.196.77.157<br/>
content-type : application/x-www-form-urlencoded<br/>
accept : */*<br/>
content-length : 12<br/>
user-agent : curl/7.53.0<br/>

获取http的其他方法

vim /usr/example/lua/lua_request.lua ,在原有的代码基础上,再添加一下代码:

ngx.say("ngx.req.http_version : ", ngx.req.http_version(), "<br/>")  
--请求方法  
ngx.say("ngx.req.get_method : ", ngx.req.get_method(), "<br/>")  
--原始的请求头内容  
ngx.say("ngx.req.raw_header : ",  ngx.req.raw_header(), "<br/>")  
--请求的body内容体  
ngx.say("ngx.req.get_body_data() : ", ngx.req.get_body_data(), "<br/>")  
ngx.say("<br/>") 

重新加载nginx -s reload

使用curl模拟请求:

curl ‘http://116.196.177.123/lua_request?a=323&b=ss’ -d ‘c=12w&d=2se3’
//....
ngx.req.http_version : 1.1<br/>
ngx.req.get_method : POST<br/>
ngx.req.raw_header : POST /lua_request?a=323&b=ss HTTP/1.1
Host: 116.196.77.157
User-Agent: curl/7.53.0
Accept: */*
Content-Length: 12

输出响应

vim /usr/example/example.conf,添加一个location,代码如下:

 location /lua_response{
        default_type 'text/html';
        lua_code_cache off;
        content_by_lua_file /usr/example/lua/lua_response.lua ;
  }

vim /usr/example/lua/lua_response.lua 添加一下代码:

ngx.header.a="1"
ngx.header.b={"a","b"}
ngx.say("hello","</br>")
ngx.print("sss")
return ngx.exit(200)

上述代码中有以下api:

  • ngx.header 向响应头输出内容
  • ngx.say 输出响应体
  • ngx.print输出响应体
  • ngx.exit 指定http状态码退出

使用curl模拟请求, curl ‘http://116.196.177.123/lua_response’ ,获取的响应体如下:

hello
sss

日志输出

在配置文件vim /usr/example/example.conf 加上以下代码:

 location /lua_log{
       default_type 'text/html';
       lua_code_cache off;
       content_by_lua_file  /usr/example/lua/lua_log.lua;
  }

vim /usr/example/lua/lua_log.lua ,加上以下代码:

local log="i'm log"
local num =10
ngx.log(ngx.ERR, "log",log)
ngx.log(ngx.INFO,"num:" ,num)

重新加载配置文件nginx -s reload

curl ‘http://116.196.177.123/lua_log’

打开nginx 的logs目录下的error.log 文件:

tail -fn 1000 /usr/servers/nginx/logs/error.log

可以看到在日志文件中已经输出了日志,这种日志主要用于记录和测试。

日志级别:

  • ngx.STDERR – 标准输出
  • ngx.EMERG – 紧急报错
  • ngx.ALERT – 报警
  • ngx.CRIT – 严重,系统故障,触发运维告警系统
  • ngx.ERR – 错误,业务不可恢复性错误
  • ngx.WARN – 告警,业务中可忽略错误
  • ngx.NOTICE – 提醒,业务比较重要信息
  • ngx.INFO – 信息,业务琐碎日志信息,包含不同情况判断等
  • ngx.DEBUG – 调试

内部调用

vim /usr/example/example.conf 添加以下代码:

location /lua_sum{
      # 只允许内部调用
      internal;
      # 这里做了一个求和运算只是一个例子,可以在这里完成一些数据库、
      # 缓存服务器的操作,达到基础模块和业务逻辑分离目的
      content_by_lua_block {
         local args = ngx.req.get_uri_args()
         ngx.say(tonumber(args.a) + tonumber(args.b))
      }
  }

internal 关键字,表示只允许内部调用。使用curl模拟请求,请求命令如下:

$ curl ‘http://116.196.177.123/lua_sum?a=1&b=2’

由于该loction是一个内部调用的,外部不能返回,最终返回的结果为404,如下:

<html>
<head><title>404 Not Found</title></head>
<body bgcolor="white">
<center><h1>404 Not Found</h1></center>
<hr><center>openresty/1.11.2.4</center>
</body>
</html>

vim /usr/example/example.conf 添加以下代码:

location = /lua_sum_test {
   content_by_lua_block {
      local res = ngx.location.capture("/lua_sum", {args={a=3, b=8}})
      ngx.say("status:", res.status, " response:", res.body)
   }  
}

上述的代码通过ngx.location.capture去调用内部的location,并获得返回结果,最终将结果输出,采用curl模拟请求:

$ curl ‘http://116.196.177.123/lua_sum_test’

返回结果如下:

status:200 response:11

重定向

vim /usr

location /lua_redirect{
    default_type 'text/html';
    content_by_lua_file  /usr/example/lua/lua_redirect.lua;
}  
ngx.redirect("http://www.fangzhipeng.com", 302)

http://116.196.177.123/lua_redirect

共享内存

vim /usr/servers/nginx/cong/nginx.conf

在http模块加上以下:

lua_shared_dict shared_data 1m;
 location /lua_shared_dict{
     default_type 'text/html';
     content_by_lua_file /usr/example/lua/lua_shared_dict.lua;
  }
local shared_data = ngx.shared.shared_data
local i = shared_data:get("i")
if not i then
  i = 1
  shared_data:set("i",i)
end
i = shared_data:incr("i",1)
ngx.say("i:",i)

多次访问 http://116.196.177.123/lua_shared_dict,浏览器打印:

i:1
i:2
i:3
i:4
i:5

OpenResty执行阶段的概念

以下内容来自于《openresty 最佳实践》

未分类

如上图所示,openresty的执行阶段分为

这样我们就可以根据我们的需要,在不同的阶段直接完成大部分典型处理了。

  • set_by_lua* : 流程分支处理判断变量初始化
  • rewrite_by_lua* : 转发、重定向、缓存等功能(例如特定请求代理到外网)
  • access_by_lua* : IP 准入、接口权限等情况集中处理(例如配合 iptable 完成简单防火墙)
  • content_by_lua* : 内容生成
  • header_filter_by_lua* : 响应头部过滤处理(例如添加头部信息)
  • body_filter_by_lua* : 响应体过滤处理(例如完成应答内容统一成大写)

执行阶段概念:

  • log_by_lua* : 会话完成后本地异步完成日志记录(日志可以记录在本地,还可以同步到其 他机器)
    实际上我们只使用其中一个阶段
  • content_by_lua* ,也可以完成所有的处理。但这样做,会让 我们的代码比较臃肿,越到后期越发难以维护。把我们的逻辑放在不同阶段,分工明确,代 码独立,后期发力可以有很多有意思的玩法。

gitlab API使用批量创建用户

gitlab有api的接口,网上搜索了一下使用的情况,貌似不多,找到的文章主要是用来批量操作用户。

下面就这个批量创建用户来测试一下

获取Access Token

未分类

【Settings】

未分类

【Access Tokens】

最后点击创建

之后会出现Access Token

未分类

我这里的Access Token为PknmemyqpPumLsKq_ytW,记录一下,下面的sh脚本里要使用

批量创建用户脚本

创建userinfo.txt

12345678 [email protected] test1 张三
12345678 [email protected] test2 李四

对应的列分别是密码,邮箱,gitlab用户名,别名,一行对应一个用户

创建gitlabAddUser.sh

#!/bin/bash
#gitlab用户文本
userinfo="userinfo.txt"
while read line 
do
    password=`echo $line | awk '{print $1}'`
    mail=`echo $line | awk '{print $2}'`
    username=`echo $line | awk '{print $3}'`
    name=`echo $line | awk '{print $4}'`
    curl -d "password=$password&email=$mail&username=$username&name=$name&private_token=PknmemyqpPumLsKq_ytW" "http://gitlab.phpsong.com/api/v4/users"

done <$userinfo

执行脚本

chmod +x gitlabAddUser.sh 
sh gitlabAddUser.sh

之后去gitlab查用户就能看到用户

未分类

K8S APISERVER源码: API注册主体流程

基于版本 1.6.7

未分类

k8s使用了go-restful github, 在前面, 已经介绍了container如何初始化的.

这里, 需要关注, api是如何注册进来的. 即, route -> webservice -> container

begin

  • pkg/master/master.go
func (c completedConfig) New() (*Master, error) {

   //  register /api
   m.InstallLegacyAPI(c.Config, c.Config.GenericConfig.RESTOptionsGetter, legacyRESTStorageProvider)
   //  register /apis
   m.InstallAPIs(c.Config.APIResourceConfigSource, c.Config.GenericConfig.RESTOptionsGetter, restStorageProviders...)
}

1. /api

  • pkg/master/master.go
func (m *Master) InstallLegacyAPI(c *Config, restOptionsGetter generic.RESTOptionsGetter, legacyRESTStorageProvider corerest.LegacyRESTStorageProvider) {
    legacyRESTStorage, apiGroupInfo, err := legacyRESTStorageProvider.NewLegacyRESTStorage(restOptionsGetter)
    m.GenericAPIServer.InstallLegacyAPIGroup(genericapiserver.DefaultLegacyAPIPrefix, &apiGroupInfo)
  • vendor/k8s.io/apiserver/pkg/server/genericapiserver.go
func (s *GenericAPIServer) InstallLegacyAPIGroup(apiPrefix string, apiGroupInfo *APIGroupInfo) error {
    s.installAPIResources(apiPrefix, apiGroupInfo)
}

2. /apis

  • pkg/master/master.go
func (m *Master) InstallAPIs(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter, restStorageProviders ...RESTStorageProvider) {
    for i := range apiGroupsInfo {
        m.GenericAPIServer.InstallAPIGroup(&apiGroupsInfo[i])   }
}
  • vendor/k8s.io/apiserver/pkg/server/genericapiserver.go
func (s *GenericAPIServer) InstallAPIGroup(apiGroupInfo *APIGroupInfo) error {
    s.installAPIResources(APIGroupPrefix, apiGroupInfo)
}

3. all to installAPIResources

vendor/k8s.io/apiserver/pkg/server/genericapiserver.go
func (s *GenericAPIServer) installAPIResources(apiPrefix string, apiGroupInfo *APIGroupInfo) error {
    for _, groupVersion := range apiGroupInfo.GroupMeta.GroupVersions {
        apiGroupVersion.InstallREST(s.HandlerContainer.Container)
    }
}
  • vendor/k8s.io/apiserver/pkg/endpoints/groupversion.go
func (g *APIGroupVersion) InstallREST(container *restful.Container) error {
    installer := g.newInstaller()
    // 新建一个WebService
    ws := installer.NewWebService()

    // 关键, URL注册, add router into ws
    apiResources, registrationErrors := installer.Install(ws)
    lister := g.ResourceLister
    if lister == nil {
        lister = staticLister{apiResources}
    }
    AddSupportedResourcesWebService(g.Serializer, ws, g.GroupVersion, lister)

    // container.add(webservice)
    container.Add(ws)
    return utilerrors.NewAggregate(registrationErrors)
}

在这里

  1. 新建一个WebService
  2. 由installer.Install(ws)将API 对应的route新建初始化后, 加入到 WebService
  3. 将WebService加入到Container
    完成了router -> webservice -> container的流程

后面, 分析 installer.Install(ws) 具体做了哪些事情(vendor/k8s.io/apiserver/pkg/endpoints/installer.go)