├── .gitignore ├── Makefile ├── README.md ├── build ├── build.sh ├── cmd ├── migrate │ └── main.go └── server │ └── main.go ├── config ├── config-prod.toml └── config.toml ├── docs ├── docs.go ├── swagger.json └── swagger.yaml ├── go-ops.service ├── go.mod ├── go.sum ├── imgs ├── vm1.png └── vm2.png ├── internal ├── api │ ├── message │ │ └── README.md │ ├── multi_cloud │ │ ├── README.md │ │ ├── account.go │ │ ├── instance.go │ │ └── template.go │ ├── nginx │ │ └── README.md │ └── scheduler │ │ └── dag.go ├── app │ └── server.go ├── cron │ └── index.go ├── db │ ├── db.go │ └── pagination.go ├── models │ ├── base.go │ ├── multi_cloud │ │ ├── README.md │ │ ├── account.go │ │ ├── instance.go │ │ └── template.go │ └── scheduler │ │ ├── dag.go │ │ └── task_instance.go ├── redis │ └── index.go ├── router │ ├── middleware │ │ ├── auth.go │ │ ├── cors.go │ │ ├── life_cycle.go │ │ └── rid.go │ └── router.go └── service │ └── scheduler │ └── serializer.go ├── pkg ├── multi_cloud_sdk │ ├── README.md │ ├── ali.go │ ├── aws.go │ ├── core.go │ ├── gmap.go │ ├── hw.go │ └── ten.go ├── request │ └── request.go ├── tools │ ├── convert.go │ ├── hash.go │ ├── msg.go │ └── print.go └── 三方调用 └── scripts ├── cron └── cron_sync_instance.go └── 业务脚本 /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/go,vim,linux,macos,emacs,pycharm,windows,sublimetext 3 | # Edit at https://www.gitignore.io/?templates=go,vim,linux,macos,emacs,pycharm,windows,sublimetext 4 | 5 | ### Emacs ### 6 | # -*- mode: gitignore; -*- 7 | *~ 8 | \#*\# 9 | /.emacs.desktop 10 | /.emacs.desktop.lock 11 | *.elc 12 | auto-save-list 13 | tramp 14 | .\#* 15 | 16 | # Org-mode 17 | .org-id-locations 18 | *_archive 19 | 20 | # flymake-mode 21 | *_flymake.* 22 | 23 | # eshell files 24 | /eshell/history 25 | /eshell/lastdir 26 | 27 | # elpa packages 28 | /elpa/ 29 | 30 | # reftex files 31 | *.rel 32 | 33 | # AUCTeX auto folder 34 | /auto/ 35 | 36 | # cask packages 37 | .cask/ 38 | dist/ 39 | 40 | # Flycheck 41 | flycheck_*.el 42 | 43 | # server auth directory 44 | /server/ 45 | 46 | # projectiles files 47 | .projectile 48 | 49 | # directory configuration 50 | .dir-locals.el 51 | 52 | # network security 53 | /network-security.data 54 | 55 | 56 | ### Go ### 57 | # Binaries for programs and plugins 58 | *.exe 59 | *.exe~ 60 | *.dll 61 | *.so 62 | *.dylib 63 | 64 | # Test binary, built with `go test -c` 65 | *.test 66 | 67 | # Output of the go coverage tool, specifically when used with LiteIDE 68 | *.out 69 | 70 | # Dependency directories (remove the comment below to include it) 71 | # vendor/ 72 | 73 | ### Go Patch ### 74 | /vendor/ 75 | /Godeps/ 76 | 77 | ### Linux ### 78 | 79 | # temporary files which can be created if a process still has a handle open of a deleted file 80 | .fuse_hidden* 81 | 82 | # KDE directory preferences 83 | .directory 84 | 85 | # Linux trash folder which might appear on any partition or disk 86 | .Trash-* 87 | 88 | # .nfs files are created when an open file is removed but is still being accessed 89 | .nfs* 90 | 91 | ### macOS ### 92 | # General 93 | .DS_Store 94 | .AppleDouble 95 | .LSOverride 96 | 97 | # Icon must end with two \r 98 | Icon 99 | 100 | # Thumbnails 101 | ._* 102 | 103 | # Files that might appear in the root of a volume 104 | .DocumentRevisions-V100 105 | .fseventsd 106 | .Spotlight-V100 107 | .TemporaryItems 108 | .Trashes 109 | .VolumeIcon.icns 110 | .com.apple.timemachine.donotpresent 111 | 112 | # Directories potentially created on remote AFP share 113 | .AppleDB 114 | .AppleDesktop 115 | Network Trash Folder 116 | Temporary Items 117 | .apdisk 118 | 119 | ### PyCharm ### 120 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 121 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 122 | 123 | # User-specific stuff 124 | .idea/**/workspace.xml 125 | .idea/**/tasks.xml 126 | .idea/**/usage.statistics.xml 127 | .idea/**/dictionaries 128 | .idea/**/shelf 129 | 130 | # Generated files 131 | .idea/**/contentModel.xml 132 | 133 | # Sensitive or high-churn files 134 | .idea/**/dataSources/ 135 | .idea/**/dataSources.ids 136 | .idea/**/dataSources.local.xml 137 | .idea/**/sqlDataSources.xml 138 | .idea/**/dynamic.xml 139 | .idea/**/uiDesigner.xml 140 | .idea/**/dbnavigator.xml 141 | 142 | # Gradle 143 | .idea/**/gradle.xml 144 | .idea/**/libraries 145 | 146 | # Gradle and Maven with auto-import 147 | # When using Gradle or Maven with auto-import, you should exclude module files, 148 | # since they will be recreated, and may cause churn. Uncomment if using 149 | # auto-import. 150 | # .idea/modules.xml 151 | # .idea/*.iml 152 | # .idea/modules 153 | # *.iml 154 | # *.ipr 155 | 156 | # CMake 157 | cmake-build-*/ 158 | 159 | # Mongo Explorer plugin 160 | .idea/**/mongoSettings.xml 161 | 162 | # File-based project format 163 | *.iws 164 | 165 | # IntelliJ 166 | out/ 167 | 168 | # mpeltonen/sbt-idea plugin 169 | .idea_modules/ 170 | 171 | # JIRA plugin 172 | atlassian-ide-plugin.xml 173 | 174 | # Cursive Clojure plugin 175 | .idea/replstate.xml 176 | 177 | # Crashlytics plugin (for Android Studio and IntelliJ) 178 | com_crashlytics_export_strings.xml 179 | crashlytics.properties 180 | crashlytics-build.properties 181 | fabric.properties 182 | 183 | # Editor-based Rest Client 184 | .idea/httpRequests 185 | 186 | # Android studio 3.1+ serialized cache file 187 | .idea/caches/build_file_checksums.ser 188 | 189 | ### PyCharm Patch ### 190 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 191 | 192 | # *.iml 193 | # modules.xml 194 | # .idea/misc.xml 195 | # *.ipr 196 | 197 | # Sonarlint plugin 198 | .idea/**/sonarlint/ 199 | 200 | # SonarQube Plugin 201 | .idea/**/sonarIssues.xml 202 | 203 | # Markdown Navigator plugin 204 | .idea/**/markdown-navigator.xml 205 | .idea/**/markdown-navigator/ 206 | 207 | ### SublimeText ### 208 | # Cache files for Sublime Text 209 | *.tmlanguage.cache 210 | *.tmPreferences.cache 211 | *.stTheme.cache 212 | 213 | # Workspace files are user-specific 214 | *.sublime-workspace 215 | 216 | # Project files should be checked into the repository, unless a significant 217 | # proportion of contributors will probably not be using Sublime Text 218 | # *.sublime-project 219 | 220 | # SFTP configuration file 221 | sftp-config.json 222 | 223 | # Package control specific files 224 | Package Control.last-run 225 | Package Control.ca-list 226 | Package Control.ca-bundle 227 | Package Control.system-ca-bundle 228 | Package Control.cache/ 229 | Package Control.ca-certs/ 230 | Package Control.merged-ca-bundle 231 | Package Control.user-ca-bundle 232 | oscrypto-ca-bundle.crt 233 | bh_unicode_properties.cache 234 | 235 | # Sublime-github package stores a github token in this file 236 | # https://packagecontrol.io/packages/sublime-github 237 | GitHub.sublime-settings 238 | 239 | ### Vim ### 240 | # Swap 241 | [._]*.s[a-v][a-z] 242 | [._]*.sw[a-p] 243 | [._]s[a-rt-v][a-z] 244 | [._]ss[a-gi-z] 245 | [._]sw[a-p] 246 | 247 | # Session 248 | Session.vim 249 | Sessionx.vim 250 | 251 | # Temporary 252 | .netrwhist 253 | 254 | # Auto-generated tag files 255 | tags 256 | 257 | # Persistent undo 258 | [._]*.un~ 259 | 260 | # Coc configuration directory 261 | .vim 262 | 263 | ### Windows ### 264 | # Windows thumbnail cache files 265 | Thumbs.db 266 | Thumbs.db:encryptable 267 | ehthumbs.db 268 | ehthumbs_vista.db 269 | 270 | # Dump file 271 | *.stackdump 272 | 273 | # Folder config file 274 | [Dd]esktop.ini 275 | 276 | # Recycle Bin used on file shares 277 | $RECYCLE.BIN/ 278 | 279 | # Windows Installer files 280 | *.cab 281 | *.msi 282 | *.msix 283 | *.msm 284 | *.msp 285 | 286 | # Windows shortcuts 287 | *.lnk 288 | 289 | # End of https://www.gitignore.io/api/go,vim,linux,macos,emacs,pycharm,windows,sublimetext 290 | 291 | 292 | .idea 293 | __pycache__ 294 | .venv 295 | logs/* 296 | celerybeat-schedule.db 297 | celerybeat-schedule 298 | celerybeat.pid 299 | ./pkg/ 300 | ./tools/* 301 | ./tests/* 302 | data/* 303 | tests/* 304 | .idea/workspace.xml 305 | bin/* 306 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | run: 2 | @go run cmd/server/main.go --config=config/config.toml 3 | 4 | run-prod: 5 | @go run cmd/server/main.go --config=config/config-prod.toml 6 | 7 | migrate: 8 | @go run cmd/migrate/main.go --config=config/config.toml 9 | 10 | migrate-prod: 11 | @go run cmd/migrate/main.go --config=config/config-prod.toml 12 | 13 | swagger: 14 | @swag init -g cmd/server/main.go 15 | 16 | build-server-linux: 17 | @CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o bin/lightning-go cmd/server/main.go 18 | 19 | build-windows: 20 | @CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o bin/lightning-go cmd/server/main.go 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lightning-go 2 | 3 | ## 环境依赖 4 | 5 | ### 版本依赖 6 | 7 | - Go 1.14+ 8 | - MySQL 5.7+ 9 | - Redis 3.2+ 10 | 11 | 12 | ## 模块介绍 13 | - [x] 多云云主机生命周期管理 14 | - [x] 阿里云 15 | - [x] 腾讯云 16 | - [ ] 华为云 17 | - [ ] 亚马逊 18 | - [ ] 青云 19 | - [ ] 消息中心 20 | - [ ] 邮件 21 | - [ ] 短信 22 | - [ ] 阿里云短信 23 | - [ ] 腾讯云短信 24 | - [ ] 电话 25 | - [ ] 阿里云语音电话 26 | - [ ] 腾讯云语音电话 27 | - [ ] 社交工具 28 | - [ ] 钉钉 29 | - [ ] 微信 30 | - [X] 任务调度 31 | - [X] 触发Dag任务 32 | - [X] 查询Dag详情 33 | - [ ] 查看任务日志 34 | - [ ] 定时任务 35 | 36 | 37 | 38 | vm1 39 | 40 | vm2 41 | 42 | ## 项目目录结构 43 | 44 | ```bash 45 | ├── README.md 46 | ├── build.sh 47 | ├── cmd # 项目入口 48 | │   ├── migrate # 创建表 49 | │   └── server # 启动服务 50 | ├── config # 配置文件 51 | │   ├── config.toml # 配置文件 52 | ├── go-ops.service # 服务systemd文件 53 | ├── go.mod 54 | ├── go.sum 55 | ├── internal # 内部依赖 56 | │   ├── app # 应用 57 | │   ├── cron # 定时任务 58 | │   ├── db # 数据库 59 | │   └── http # http 60 | ├── logs # 日志目录 61 | ├── pkg 62 | ├── scripts 63 | └── test # 测试用例 64 | ``` 65 | 66 | ## 部署 67 | 68 | - 克隆代码 69 | ```bash 70 | $ git clone git@github.com:zhengyansheng/lightning-go.git 71 | ``` 72 | 73 | ```bash 74 | $ cd lightning-go 75 | $ go mod init lightning-go 76 | 77 | ``` 78 | 79 | - 同步数据库 80 | ```bash 81 | # make migrate 82 | ``` 83 | 84 | - 启动服务 85 | ```bash 86 | # make run 87 | ``` 88 | 89 | - 生成 api docs 90 | ```bash 91 | # make swagger 92 | ``` 93 | 94 | ## 定时任务 95 | 96 | - 同步云主机元数据 97 | - 同步新增 98 | - 变更 99 | - 通知异常 100 | 101 | ```bash 102 | $ go run scripts/cron/cron_sync_instance.go 103 | ``` 104 | -------------------------------------------------------------------------------- /build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -xe 3 | LDFLAGS="-s -w" 4 | rm -rf output 5 | mkdir -p output 6 | TMP='/tmp' 7 | VERSION='1.0.0' 8 | 9 | function buildProduct() { 10 | echo "build go-ops beging" 11 | git pull 12 | go build -ldflags "$LDFLAGS" -o go-ops cmd/server/main.go 13 | mv kjcloud output 14 | systemctl restart go-ops 15 | } 16 | 17 | function _help() { 18 | echo "Welcome to go-ops build system" 19 | } 20 | function main() { 21 | 22 | _help 23 | select ch in "go-ops"; 24 | do 25 | case $ch in 26 | "go-ops" ) buildProduct 27 | break;; 28 | 29 | *) echo "please choose a number" 30 | break;; 31 | esac 32 | done 33 | } 34 | main -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -xe 3 | LDFLAGS="-s -w" 4 | rm -rf output 5 | mkdir -p output 6 | TMP='/tmp' 7 | VERSION='1.0.0' 8 | 9 | function buildProduct() { 10 | echo "build go-ops beging" 11 | git pull 12 | go build -ldflags "$LDFLAGS" -o go-ops cmd/server/main.go 13 | mv kjcloud output 14 | systemctl restart go-ops 15 | } 16 | 17 | function _help() { 18 | echo "Welcome to go-ops build system" 19 | } 20 | function main() { 21 | 22 | _help 23 | select ch in "go-ops"; 24 | do 25 | case $ch in 26 | "go-ops" ) buildProduct 27 | break;; 28 | 29 | *) echo "please choose a number" 30 | break;; 31 | esac 32 | done 33 | } 34 | main -------------------------------------------------------------------------------- /cmd/migrate/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/douyu/jupiter" 7 | "github.com/douyu/jupiter/pkg/conf" 8 | _ "github.com/go-sql-driver/mysql" 9 | "github.com/jinzhu/gorm" 10 | 11 | "lightning-go/internal/db" 12 | "lightning-go/internal/models/multi_cloud" 13 | ) 14 | 15 | const RECREATE = true 16 | 17 | // Admin indicates the user is a system administrator. 18 | type InstallAPP struct { 19 | jupiter.Application 20 | } 21 | 22 | func main() { 23 | eng := &InstallAPP{} 24 | _ = eng.Startup( 25 | eng.initApp, 26 | ) 27 | initDB() 28 | db.Init() 29 | 30 | } 31 | 32 | func initDB() error { 33 | var dbName = "chaos" 34 | gormdb, err := gorm.Open( 35 | "mysql", 36 | conf.GetString("jupiter.mysql.master.dsn"), 37 | ) 38 | 39 | if err != nil { 40 | return err 41 | } 42 | if RECREATE { 43 | var result []struct { 44 | Sqlstr string 45 | } 46 | gormdb.Debug().Raw("SELECT concat('DROP TABLE IF EXISTS `', table_name, '`;') as sqlstr FROM information_schema.tables WHERE table_schema = '" + dbName + "'").Scan(&result) 47 | for _, v := range result { 48 | fmt.Println(`sql drop:`, v.Sqlstr) 49 | 50 | gormdb.Exec(v.Sqlstr) 51 | } 52 | } 53 | 54 | defer func() { 55 | _ = gormdb.Close() 56 | }() 57 | 58 | models := []interface{}{ 59 | &multi_cloud.CloudTemplate{}, 60 | &multi_cloud.Account{}, 61 | &multi_cloud.InstanceLifeCycle{}, 62 | } 63 | if RECREATE { 64 | gormdb.DropTableIfExists(models...) 65 | } 66 | // 67 | 68 | // 删除原来的表 69 | gormdb.SingularTable(true) 70 | gormdb.Set("gorm:table_options", "ENGINE=InnoDB").AutoMigrate(models...) 71 | fmt.Println("create table ok") 72 | return nil 73 | 74 | } 75 | 76 | func (eng *InstallAPP) initApp() error { 77 | return nil 78 | } 79 | -------------------------------------------------------------------------------- /cmd/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/douyu/jupiter/pkg/xlog" 5 | "lightning-go/internal/app" 6 | ) 7 | 8 | // @title lightning-go api 9 | // @version 1.0 10 | // @description lightning-go api 11 | // @termsOfService https://github.com/zhengyansheng/lightning-go 12 | // @license.name MIT 13 | // @license.url https://github.com/zhengyansheng/lightning-go 14 | 15 | func main() { 16 | 17 | eng := app.NewEngine() 18 | if err := eng.Run(); err != nil { 19 | xlog.Error("service could not run", xlog.FieldErr(err)) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /config/config-prod.toml: -------------------------------------------------------------------------------- 1 | [jupiter.server.http] 2 | host = "127.0.0.1" 3 | port = 9900 4 | 5 | [jupiter.logger.default] 6 | enableConsole = true 7 | level = "debug" 8 | debug = true 9 | MaxAge = 2 10 | MaxBackup = 10 11 | Dir = "./logs/" 12 | Async = false 13 | 14 | [jupiter.mysql.master] 15 | connMaxLifetime = "300s" 16 | debug = true 17 | dsn = "root:123456@tcp(127.0.0.1:3306)/lightning-go?charset=utf8&parseTime=True&loc=Local&readTimeout=5s&timeout=5s&writeTimeout=3s" 18 | level = "panic" 19 | maxIdleConns = 50 20 | maxOpenConns = 100 21 | 22 | [jupiter.mysql.airflow] 23 | connMaxLifetime = "300s" 24 | debug = true 25 | dsn = "root:123456@tcp(127.0.0.1:3306)/airflow?charset=utf8&parseTime=True&loc=Local&readTimeout=5s&timeout=5s&writeTimeout=3s" 26 | level = "panic" 27 | maxIdleConns = 50 28 | maxOpenConns = 100 29 | 30 | 31 | [jupiter.cron.chaos] 32 | withSeconds = true 33 | immediatelyRun = false 34 | concurrentDelay = -1 35 | 36 | 37 | [jupiter.server.governor] 38 | enable = false 39 | host = "0.0.0.0" 40 | port = 9800 41 | 42 | 43 | [go-ops.hosts] 44 | auth = "http://121.4.224.236:8000/api/v1/has-perm" 45 | 46 | [go-ops.scheduler] 47 | airflowUrl = "http://121.4.224.236:8888" 48 | airflowUserName = "admin" 49 | airflowUserPassword = "zhengyansheng" 50 | -------------------------------------------------------------------------------- /config/config.toml: -------------------------------------------------------------------------------- 1 | [jupiter.server.http] 2 | host = "127.0.0.1" 3 | port = 9900 4 | 5 | [jupiter.logger.default] 6 | enableConsole = true 7 | level = "debug" 8 | debug = true 9 | MaxAge = 2 10 | MaxBackup = 10 11 | Dir = "./log/" 12 | Async = false 13 | 14 | [jupiter.mysql.master] 15 | connMaxLifetime = "300s" 16 | debug = true 17 | dsn = "root:12345678@tcp(192.168.3.53:3306)/lightning-go?charset=utf8&parseTime=True&loc=Local&readTimeout=5s&timeout=5s&writeTimeout=3s" 18 | level = "panic" 19 | maxIdleConns = 50 20 | maxOpenConns = 100 21 | 22 | [jupiter.mysql.airflow] 23 | connMaxLifetime = "300s" 24 | debug = true 25 | dsn = "zhengyansheng:zhengyansheng@2021@tcp(www.aiops724.com:3306)/airflow?charset=utf8&parseTime=True&loc=Local&readTimeout=5s&timeout=5s&writeTimeout=3s" 26 | level = "panic" 27 | maxIdleConns = 50 28 | maxOpenConns = 100 29 | 30 | 31 | [jupiter.cron.chaos] 32 | withSeconds = true 33 | immediatelyRun = false 34 | concurrentDelay = -1 35 | 36 | 37 | [jupiter.server.governor] 38 | enable = false 39 | host = "0.0.0.0" 40 | port = 9800 41 | 42 | 43 | [go-ops.hosts] 44 | auth = "http://121.4.224.236:8000/api/v1/has-perm" 45 | 46 | [go-ops.scheduler] 47 | airflowUrl = "http://121.4.224.236:8888" 48 | airflowUserName = "admin" 49 | airflowUserPassword = "zhengyansheng" 50 | -------------------------------------------------------------------------------- /docs/docs.go: -------------------------------------------------------------------------------- 1 | // GENERATED BY THE COMMAND ABOVE; DO NOT EDIT 2 | // This file was generated by swaggo/swag 3 | 4 | package docs 5 | 6 | import ( 7 | "bytes" 8 | "encoding/json" 9 | "strings" 10 | 11 | "github.com/alecthomas/template" 12 | "github.com/swaggo/swag" 13 | ) 14 | 15 | var doc = `{ 16 | "schemes": {{ marshal .Schemes }}, 17 | "swagger": "2.0", 18 | "info": { 19 | "description": "{{.Description}}", 20 | "title": "{{.Title}}", 21 | "termsOfService": "https://github.com/zhengyansheng/lightning-go", 22 | "contact": {}, 23 | "license": { 24 | "name": "MIT", 25 | "url": "https://github.com/zhengyansheng/lightning-go" 26 | }, 27 | "version": "{{.Version}}" 28 | }, 29 | "host": "{{.Host}}", 30 | "basePath": "{{.BasePath}}", 31 | "paths": {} 32 | }` 33 | 34 | type swaggerInfo struct { 35 | Version string 36 | Host string 37 | BasePath string 38 | Schemes []string 39 | Title string 40 | Description string 41 | } 42 | 43 | // SwaggerInfo holds exported Swagger Info so clients can modify it 44 | var SwaggerInfo = swaggerInfo{ 45 | Version: "1.0", 46 | Host: "", 47 | BasePath: "", 48 | Schemes: []string{}, 49 | Title: "lightning-go api", 50 | Description: "lightning-go api", 51 | } 52 | 53 | type s struct{} 54 | 55 | func (s *s) ReadDoc() string { 56 | sInfo := SwaggerInfo 57 | sInfo.Description = strings.Replace(sInfo.Description, "\n", "\\n", -1) 58 | 59 | t, err := template.New("swagger_info").Funcs(template.FuncMap{ 60 | "marshal": func(v interface{}) string { 61 | a, _ := json.Marshal(v) 62 | return string(a) 63 | }, 64 | }).Parse(doc) 65 | if err != nil { 66 | return doc 67 | } 68 | 69 | var tpl bytes.Buffer 70 | if err := t.Execute(&tpl, sInfo); err != nil { 71 | return doc 72 | } 73 | 74 | return tpl.String() 75 | } 76 | 77 | func init() { 78 | swag.Register(swag.Name, &s{}) 79 | } 80 | -------------------------------------------------------------------------------- /docs/swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "description": "lightning-go api", 5 | "title": "lightning-go api", 6 | "termsOfService": "https://github.com/zhengyansheng/lightning-go", 7 | "contact": {}, 8 | "license": { 9 | "name": "MIT", 10 | "url": "https://github.com/zhengyansheng/lightning-go" 11 | }, 12 | "version": "1.0" 13 | }, 14 | "paths": {} 15 | } -------------------------------------------------------------------------------- /docs/swagger.yaml: -------------------------------------------------------------------------------- 1 | info: 2 | contact: {} 3 | description: lightning-go api 4 | license: 5 | name: MIT 6 | url: https://github.com/zhengyansheng/lightning-go 7 | termsOfService: https://github.com/zhengyansheng/lightning-go 8 | title: lightning-go api 9 | version: "1.0" 10 | paths: {} 11 | swagger: "2.0" 12 | -------------------------------------------------------------------------------- /go-ops.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description= Go-OPS Service. 3 | ConditionFileIsExecutable=/data/go-ops/output/kjcloud 4 | 5 | [Service] 6 | StartLimitInterval=5 7 | StartLimitBurst=10 8 | ExecStart=/data/go-ops/output/go-ops --config=/data/go-ops/config/server.toml 9 | StandardOutput=file:/data/go-ops/log/tapi.log 10 | 11 | Restart=always 12 | RestartSec=120 13 | EnvironmentFile=-/etc/sysconfig/TPApi 14 | 15 | [Install] 16 | WantedBy=multi-user.target 17 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module lightning-go 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 7 | github.com/alibabacloud-go/darabonba-openapi v0.1.5 8 | github.com/alibabacloud-go/ecs-20140526/v2 v2.0.2 9 | github.com/alibabacloud-go/tea v1.1.15 10 | github.com/douyu/jupiter v0.2.9 11 | github.com/fatih/structs v1.1.0 12 | github.com/gin-gonic/gin v1.6.3 13 | github.com/go-openapi/spec v0.20.3 // indirect 14 | github.com/go-openapi/swag v0.19.15 // indirect 15 | github.com/go-resty/resty/v2 v2.2.0 16 | github.com/go-sql-driver/mysql v1.5.0 17 | github.com/jinzhu/gorm v1.9.12 18 | github.com/mailru/easyjson v0.7.7 // indirect 19 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 20 | github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b 21 | github.com/swaggo/gin-swagger v1.2.0 22 | github.com/swaggo/swag v1.7.0 23 | github.com/tencentcloud/tencentcloud-sdk-go v1.0.128 24 | github.com/tidwall/gjson v1.2.1 25 | golang.org/x/net v0.0.0-20210331212208-0fccb6fa2b5c // indirect 26 | golang.org/x/sys v0.0.0-20210402192133-700132347e07 // indirect 27 | golang.org/x/text v0.3.6 // indirect 28 | golang.org/x/tools v0.1.0 // indirect 29 | ) 30 | -------------------------------------------------------------------------------- /imgs/vm1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengyansheng/lightning-go/4f66fb5cf55e4c989392a550f14df926b4be93d5/imgs/vm1.png -------------------------------------------------------------------------------- /imgs/vm2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengyansheng/lightning-go/4f66fb5cf55e4c989392a550f14df926b4be93d5/imgs/vm2.png -------------------------------------------------------------------------------- /internal/api/message/README.md: -------------------------------------------------------------------------------- 1 | # 消息中心 2 | 3 | > 消息通知: 4 | > 为其它API提供最底层的统一消息网关,比如 工单,监控,任务异常等 5 | 6 | ## 需求 7 | 8 | - 发送渠道类型 9 | - 发送内容及相关信息保存数据库,方便页面展示分析 10 | - API接口设计可统一,通过类型来区分不同的发送渠道,尽量让接口用起来更简单 11 | - 扩展: 发送内容,服务端支持模版格式选择 12 | 13 | ### 发送消息 14 | ```text 15 | POST /api/v1/message 16 | Header 17 | Content-Type: application/json 18 | Authorization: JWT xxxxx 19 | 20 | { 21 | "channel": "email/ short_letter/ phone/ ding/ wechat" 22 | "data": { 23 | 24 | } 25 | } 26 | ``` 27 | 28 | 注意 29 | 1. 如果需要用到第三方ak/sk,需要本地建表存储。 30 | 31 | ### 查询消息 32 | ```text 33 | GET /api/v1/message 34 | Header 35 | Content-Type: application/json 36 | Authorization: JWT xxxxx 37 | 38 | { 39 | "code": 0, 40 | "message": nil, 41 | "data": [] 42 | } 43 | ``` 44 | -------------------------------------------------------------------------------- /internal/api/multi_cloud/README.md: -------------------------------------------------------------------------------- 1 | # 多云管理 2 | 3 | ## 模版 4 | 5 | - 环境节点能够基本操作 6 | - 创建/删除模版,交付机器。 7 | - 非环境节点只能查看模版 8 | - 多展示AppKey字段用来区分 9 | 10 | 11 | ## 交付 12 | 13 | - 触发 Airflow Dag 14 | 15 | ```json 16 | { 17 | "data": { 18 | "account": "ali.lightning", 19 | "pay_type": "PostPaid", 20 | "region_id": "cn-beijing", 21 | "zone_id": "cn-beijing-c", 22 | "instance_type": "ecs.sn1.medium", 23 | "image_id": "centos_7_8_x64_20G_alibase_20200914.vhd", 24 | "vpc_id": "vpc-2ze80et76jwcsuc2asobq", 25 | "subnet_id": "vsw-2zelzctt7aougw3bhz5ev", 26 | "disks": [{ 27 | "disk_type": "system", 28 | "disk_storage_type": "cloud", 29 | "disk_storage_size": 100 30 | }], 31 | "security_group_ids": ["sg-2zeb9n0qzygmjwn7hwae"], 32 | "hostname": "lightning-fe2.ops.prod.ali" 33 | } 34 | } 35 | ``` -------------------------------------------------------------------------------- /internal/api/multi_cloud/account.go: -------------------------------------------------------------------------------- 1 | package multi_cloud 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "lightning-go/internal/models/multi_cloud" 7 | "lightning-go/pkg/tools" 8 | 9 | "github.com/gin-gonic/gin" 10 | ) 11 | 12 | func CreateAccountView(c *gin.Context) { 13 | // Validate field 14 | var s multi_cloud.Account 15 | if err := c.ShouldBindJSON(&s); err != nil { 16 | tools.JSONFailed(c, tools.MSG_ERR, err.Error()) 17 | return 18 | } 19 | // Create 20 | err := s.Create() 21 | if err != nil { 22 | tools.JSONFailed(c, tools.MSG_ERR, err.Error()) 23 | return 24 | } 25 | // Response 26 | tools.JSONOk(c, "Create ok.") 27 | } 28 | 29 | func ListAccountView(c *gin.Context) { 30 | var s multi_cloud.Account 31 | // List 32 | accountsInfo, err := s.List() 33 | if err != nil { 34 | tools.JSONFailed(c, tools.MSG_ERR, err.Error()) 35 | return 36 | } 37 | // Response 38 | tools.JSONOk(c, accountsInfo) 39 | } 40 | 41 | func DeleteAccountView(c *gin.Context) { 42 | var s multi_cloud.Account 43 | pk := c.Param("id") 44 | pkUint, _ := tools.StringToUint(pk) 45 | s.ID = pkUint 46 | 47 | // Delete 48 | err := s.Delete() 49 | if err != nil { 50 | tools.JSONFailed(c, tools.MSG_ERR, err.Error()) 51 | return 52 | } 53 | // Response 54 | tools.JSONOk(c, "Delete ok.") 55 | } 56 | 57 | func UpdateAccountView(c *gin.Context) { 58 | var s multi_cloud.Account 59 | pk := c.Param("id") 60 | pkUint, _ := tools.StringToUint(pk) 61 | s.ID = pkUint 62 | 63 | // Update 64 | bytes, err := ioutil.ReadAll(c.Request.Body) 65 | if err != nil { 66 | tools.JSONFailed(c, tools.MSG_ERR, err.Error()) 67 | return 68 | } 69 | data := make(map[string]interface{}) 70 | err = json.Unmarshal(bytes, &data) 71 | if err != nil { 72 | tools.JSONFailed(c, tools.MSG_ERR, err.Error()) 73 | return 74 | } 75 | err = s.Update(data) 76 | if err != nil { 77 | tools.JSONFailed(c, tools.MSG_ERR, err.Error()) 78 | return 79 | } 80 | // Response 81 | tools.JSONOk(c, "", "Update ok.") 82 | 83 | } 84 | -------------------------------------------------------------------------------- /internal/api/multi_cloud/instance.go: -------------------------------------------------------------------------------- 1 | package multi_cloud 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gin-gonic/gin" 6 | "lightning-go/internal/models/multi_cloud" 7 | "lightning-go/pkg/multi_cloud_sdk" 8 | "lightning-go/pkg/tools" 9 | "reflect" 10 | ) 11 | 12 | func CreateInstanceView(c *gin.Context) { 13 | // Validate field 14 | s := struct { 15 | Account string `json:"account" binding:"required"` 16 | PayType string `json:"pay_type" binding:"required"` 17 | RegionId string `json:"region_id" binding:"required"` 18 | ZoneId string `json:"zone_id" binding:"required"` 19 | InstanceType string `json:"instance_type" binding:"required"` 20 | ImageId string `json:"image_id" binding:"required"` 21 | VpcId string `json:"vpc_id" binding:"required"` 22 | SubnetId string `json:"subnet_id" binding:"required"` 23 | SecurityGroupIds []string `json:"security_group_ids" binding:"required"` 24 | Hostname string `json:"hostname" binding:"required"` 25 | DryRun bool `json:"dry_run" binding:"omitempty"` 26 | }{} 27 | if err := c.ShouldBindJSON(&s); err != nil { 28 | tools.JSONFailed(c, tools.MSG_ERR, fmt.Sprintf("ShouldBindJSON %v", err.Error())) 29 | return 30 | } 31 | // Factory CreateInstance 32 | tools.PrettyPrint(s) 33 | clt, err := multi_cloud_sdk.NewFactoryByAccount(s.Account, s.RegionId) 34 | if err != nil { 35 | tools.JSONFailed(c, tools.MSG_ERR, fmt.Sprintf("NewFactoryByAccount %v", err.Error())) 36 | return 37 | } 38 | response, err := clt.CreateInstance(s.PayType, s.Hostname, s.InstanceType, s.ZoneId, s.ImageId, s.VpcId, s.SubnetId, s.SecurityGroupIds, s.DryRun) 39 | if err != nil { 40 | tools.JSONFailed(c, tools.MSG_ERR, fmt.Sprintf("CreateInstance %v", err.Error())) 41 | return 42 | } 43 | // Response 44 | tools.JSONOk(c, response) 45 | } 46 | 47 | func StartInstanceView(c *gin.Context) { 48 | // Validate field 49 | s := struct { 50 | Account string `json:"account" binding:"required"` 51 | RegionId string `json:"region_id" binding:"required"` 52 | InstanceId string `json:"instance_id" binding:"required"` 53 | }{} 54 | if err := c.ShouldBindJSON(&s); err != nil { 55 | tools.JSONFailed(c, tools.MSG_ERR, err.Error()) 56 | return 57 | } 58 | // Factory StartInstance 59 | tools.PrettyPrint(s) 60 | clt, err := multi_cloud_sdk.NewFactoryByAccount(s.Account, s.RegionId) 61 | if err != nil { 62 | tools.JSONFailed(c, tools.MSG_ERR, err.Error()) 63 | return 64 | } 65 | response, err := clt.StartInstance(s.InstanceId) 66 | if err != nil { 67 | tools.JSONFailed(c, tools.MSG_ERR, fmt.Sprintf("StartInstance %v", err.Error())) 68 | return 69 | } 70 | // Response 71 | tools.JSONOk(c, response) 72 | } 73 | 74 | func StopInstanceView(c *gin.Context) { 75 | // Validate field 76 | s := struct { 77 | Account string `json:"account" binding:"required"` 78 | RegionId string `json:"region_id" binding:"required"` 79 | InstanceId string `json:"instance_id" binding:"required"` 80 | }{} 81 | if err := c.ShouldBindJSON(&s); err != nil { 82 | tools.JSONFailed(c, tools.MSG_ERR, err.Error()) 83 | return 84 | } 85 | // Factory StopInstance 86 | tools.PrettyPrint(s) 87 | clt, err := multi_cloud_sdk.NewFactoryByAccount(s.Account, s.RegionId) 88 | if err != nil { 89 | tools.JSONFailed(c, tools.MSG_ERR, err.Error()) 90 | return 91 | } 92 | response, err := clt.StopInstance(s.InstanceId) 93 | if err != nil { 94 | tools.JSONFailed(c, tools.MSG_ERR, fmt.Sprintf("StopInstance %v", err.Error())) 95 | return 96 | } 97 | // Response 98 | tools.JSONOk(c, response) 99 | } 100 | 101 | func RebootInstanceView(c *gin.Context) { 102 | // Validate field 103 | s := struct { 104 | Account string `json:"account" binding:"required"` 105 | RegionId string `json:"region_id" binding:"required"` 106 | InstanceId string `json:"instance_id" binding:"required"` 107 | ForceStop bool `json:"force_stop"` 108 | }{} 109 | if err := c.ShouldBindJSON(&s); err != nil { 110 | tools.JSONFailed(c, tools.MSG_ERR, err.Error()) 111 | return 112 | } 113 | // Factory RebootInstance 114 | clt, err := multi_cloud_sdk.NewFactoryByAccount(s.Account, s.RegionId) 115 | if err != nil { 116 | tools.JSONFailed(c, tools.MSG_ERR, err.Error()) 117 | return 118 | } 119 | response, err := clt.RebootInstance(s.InstanceId, s.ForceStop) 120 | if err != nil { 121 | tools.JSONFailed(c, tools.MSG_ERR, fmt.Sprintf("RebootInstance %v", err.Error())) 122 | return 123 | } 124 | // Response 125 | tools.JSONOk(c, response) 126 | } 127 | 128 | func ListInstancesView(c *gin.Context) { 129 | // Validate field 130 | s := struct { 131 | Account string `form:"account" binding:"required"` 132 | RegionId string `form:"region_id" binding:"required"` 133 | }{} 134 | if err := c.ShouldBind(&s); err != nil { 135 | tools.JSONFailed(c, tools.MSG_ERR, err.Error()) 136 | return 137 | } 138 | // Factory ListInstances 139 | tools.PrettyPrint(s) 140 | clt, err := multi_cloud_sdk.NewFactoryByAccount(s.Account, s.RegionId) 141 | if err != nil { 142 | tools.JSONFailed(c, tools.MSG_ERR, err.Error()) 143 | return 144 | } 145 | response, err := clt.ListInstances() 146 | if err != nil { 147 | tools.JSONFailed(c, tools.MSG_ERR, fmt.Sprintf("ListInstances %v", err.Error())) 148 | return 149 | } 150 | // Response 151 | tools.JSONOk(c, response) 152 | } 153 | 154 | func InstanceDetailView(c *gin.Context) { 155 | // Validate field 156 | s := struct { 157 | Account string `form:"account" binding:"required"` 158 | RegionId string `form:"region_id" binding:"required"` 159 | }{} 160 | if err := c.ShouldBind(&s); err != nil { 161 | tools.JSONFailed(c, tools.MSG_ERR, err.Error()) 162 | return 163 | } 164 | // Factory ListInstance 165 | tools.PrettyPrint(s) 166 | clt, err := multi_cloud_sdk.NewFactoryByAccount(s.Account, s.RegionId) 167 | if err != nil { 168 | tools.JSONFailed(c, tools.MSG_ERR, err.Error()) 169 | return 170 | } 171 | response, err := clt.ListInstance(c.Param("instance_id")) 172 | if err != nil { 173 | tools.JSONFailed(c, tools.MSG_ERR, fmt.Sprintf("ListInstance %v", err.Error())) 174 | return 175 | } 176 | // Response 177 | tools.JSONOk(c, response) 178 | } 179 | 180 | func ListRegionView(c *gin.Context) { 181 | // Validate field 182 | s := struct { 183 | Account string `form:"account" binding:"required"` 184 | RegionId string `form:"region_id" binding:"required"` 185 | }{} 186 | if err := c.ShouldBind(&s); err != nil { 187 | tools.JSONFailed(c, tools.MSG_ERR, err.Error()) 188 | return 189 | } 190 | // Factory ListRegions 191 | tools.PrettyPrint(s) 192 | clt, err := multi_cloud_sdk.NewFactoryByAccount(s.Account, s.RegionId) 193 | if err != nil { 194 | tools.JSONFailed(c, tools.MSG_ERR, err.Error()) 195 | return 196 | } 197 | response, err := clt.ListRegions() 198 | if err != nil { 199 | tools.JSONFailed(c, tools.MSG_ERR, fmt.Sprintf("ListRegion %v", err.Error())) 200 | return 201 | } 202 | // Response 203 | tools.JSONOk(c, response) 204 | } 205 | 206 | func DestroyInstanceView(c *gin.Context) { 207 | // Validate field 208 | s := struct { 209 | Account string `json:"account" binding:"required"` 210 | RegionId string `json:"region_id" binding:"required"` 211 | InstanceId string `json:"instance_id" binding:"required"` 212 | ForceStop bool `json:"force_stop"` 213 | }{} 214 | if err := c.ShouldBindJSON(&s); err != nil { 215 | tools.JSONFailed(c, tools.MSG_ERR, err.Error()) 216 | return 217 | } 218 | // Factory DestroyInstance 219 | tools.PrettyPrint(s) 220 | clt, err := multi_cloud_sdk.NewFactoryByAccount(s.Account, s.RegionId) 221 | if err != nil { 222 | tools.JSONFailed(c, tools.MSG_ERR, err.Error()) 223 | return 224 | } 225 | response, err := clt.DestroyInstance(s.InstanceId, s.ForceStop) 226 | if err != nil { 227 | tools.JSONFailed(c, tools.MSG_ERR, fmt.Sprintf("DestroyInstance %v", err.Error())) 228 | return 229 | } 230 | // Response 231 | tools.JSONOk(c, response) 232 | } 233 | 234 | func ModifyInstanceNameView(c *gin.Context) { 235 | // Validate field 236 | s := struct { 237 | Account string `json:"account" binding:"required"` 238 | RegionId string `json:"region_id" binding:"required"` 239 | InstanceId string `json:"instance_id" binding:"required"` 240 | InstanceName string `json:"instance_name" binding:"required"` 241 | }{} 242 | if err := c.ShouldBindJSON(&s); err != nil { 243 | tools.JSONFailed(c, tools.MSG_ERR, err.Error()) 244 | return 245 | } 246 | // Factory ModifyInstanceName 247 | tools.PrettyPrint(s) 248 | clt, err := multi_cloud_sdk.NewFactoryByAccount(s.Account, s.RegionId) 249 | if err != nil { 250 | tools.JSONFailed(c, tools.MSG_ERR, err.Error()) 251 | return 252 | } 253 | response, err := clt.ModifyInstanceName(s.InstanceId, s.InstanceName) 254 | if err != nil { 255 | tools.JSONFailed(c, tools.MSG_ERR, fmt.Sprintf("ModifyInstanceName %v", err.Error())) 256 | return 257 | } 258 | // Response 259 | tools.JSONOk(c, response) 260 | } 261 | 262 | func LifeCyclelView(c *gin.Context) { 263 | // Validate field 264 | var cycleArr = [](map[string]interface{}){} 265 | var cycle = multi_cloud.InstanceLifeCycle{} 266 | cycle.InstanceId = c.Param("instance_id") 267 | 268 | // List 269 | response, err := cycle.GetByInstanceId() 270 | if err != nil { 271 | tools.JSONFailed(c, tools.MSG_ERR, err.Error()) 272 | return 273 | } 274 | 275 | // Response 276 | for _, v := range response { 277 | t := make(map[string]interface{}) 278 | fmt.Println(v.CreatedAt, reflect.TypeOf(v.CreatedAt)) 279 | formatDateTime := v.CreatedAt.Format("2006-01-02 15:04:05") 280 | t[formatDateTime] = v.Uri 281 | cycleArr = append(cycleArr, t) 282 | } 283 | tools.JSONOk(c, cycleArr) 284 | } 285 | -------------------------------------------------------------------------------- /internal/api/multi_cloud/template.go: -------------------------------------------------------------------------------- 1 | package multi_cloud 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/gin-gonic/gin/binding" 6 | "lightning-go/internal/models/multi_cloud" 7 | "lightning-go/pkg/tools" 8 | ) 9 | 10 | func CreateTemplateView(c *gin.Context) { 11 | var ( 12 | err error 13 | template multi_cloud.CloudTemplate 14 | ) 15 | // 参数验证 16 | err = c.ShouldBindWith(&template, binding.JSON) 17 | if err != nil { 18 | tools.JSONFailed(c, tools.MSG_ERR, err.Error()) 19 | return 20 | } 21 | // 格式化打印验证的数据 22 | tools.PrettyPrint(template) 23 | 24 | // 保存 25 | err = template.Create() 26 | if err != nil { 27 | tools.JSONFailed(c, tools.MSG_ERR, err.Error()) 28 | return 29 | } 30 | 31 | // 返回 32 | tools.JSONOk(c, "Created successfully") 33 | } 34 | 35 | func CetTemplateByAppKeyView(c *gin.Context) { 36 | var ( 37 | err error 38 | template multi_cloud.CloudTemplate 39 | templates []multi_cloud.CloudTemplate 40 | ) 41 | // 参数验证 42 | appKey, ok := c.GetQuery("app_key") 43 | if !ok { 44 | tools.JSONFailed(c, tools.MSG_ERR, "app_key is required.") 45 | return 46 | } 47 | 48 | // 保存 49 | 50 | template.AppKey = appKey 51 | 52 | if v, ok := c.GetQuery("account"); ok { 53 | template.Account = v 54 | } 55 | 56 | if v, ok := c.GetQuery("region_id"); ok { 57 | template.RegionId = v 58 | } 59 | 60 | // 格式化打印验证的数据 61 | tools.PrettyPrint(template) 62 | 63 | templates, err = template.ListByAppKey() 64 | if err != nil { 65 | tools.JSONFailed(c, tools.MSG_ERR, err.Error()) 66 | return 67 | } 68 | 69 | // 返回 70 | tools.JSONOk(c, templates) 71 | } 72 | 73 | func DeleteTemplateView(c *gin.Context) { 74 | var template multi_cloud.CloudTemplate 75 | 76 | pk := c.Param("id") 77 | pkUint, _ := tools.StringToUint(pk) 78 | template.ID = pkUint 79 | err := template.Delete() 80 | if err != nil { 81 | tools.JSONFailed(c, tools.MSG_ERR, err.Error()) 82 | return 83 | } 84 | 85 | // 返回 86 | tools.JSONOk(c, "Delete ok.") 87 | } 88 | -------------------------------------------------------------------------------- /internal/api/nginx/README.md: -------------------------------------------------------------------------------- 1 | # 流量调度 2 | > nginx管理 3 | 4 | ## 核心功能 5 | 6 | - nginx server 7 | - nginx location 8 | - nginx upstream 9 | 10 | ## etcd 11 | 12 | 13 | ```text 14 | /corp///ip 15 | ``` 16 | -------------------------------------------------------------------------------- /internal/api/scheduler/dag.go: -------------------------------------------------------------------------------- 1 | package scheduler 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "lightning-go/internal/models/scheduler" 7 | "lightning-go/internal/service/scheduler" 8 | "lightning-go/pkg/tools" 9 | 10 | "strings" 11 | 12 | "github.com/gin-gonic/gin" 13 | ) 14 | 15 | // 触发Dag 16 | func TriggerDagRun(c *gin.Context) { 17 | // 接收所有参数 不做验证 18 | var dagRun service.DagRunDataSerializer 19 | dagRun.DagName = c.Param("dagName") // /:id 20 | bytes, err := ioutil.ReadAll(c.Request.Body) 21 | if err != nil { 22 | tools.JSONFailed(c, tools.MSG_ERR, err.Error()) 23 | return 24 | } 25 | paramData, err := tools.ByteToJson(bytes) 26 | if err != nil { 27 | tools.JSONFailed(c, tools.MSG_ERR, err.Error()) 28 | return 29 | } 30 | dagRun.Data = paramData 31 | msg, err := dagRun.Trigger() 32 | if err != nil { 33 | tools.JSONFailed(c, tools.MSG_ERR, err.Error()) 34 | return 35 | } 36 | tools.JSONOk(c, msg) 37 | } 38 | 39 | // 触发Dag 40 | func TriggerDagRunV2(c *gin.Context) { 41 | // Validate 42 | var dagRun service.DagRunDataSerializer 43 | if err := c.ShouldBindJSON(&dagRun); err != nil { 44 | tools.JSONFailed(c, tools.MSG_ERR, err.Error()) 45 | return 46 | } 47 | dagRun.DagName = c.Param("dagName") // /:id 48 | tools.PrettyPrint(dagRun) 49 | msg, err := dagRun.Trigger() 50 | if err != nil { 51 | tools.JSONFailed(c, tools.MSG_ERR, err.Error()) 52 | return 53 | } 54 | tools.JSONOk(c, msg) 55 | } 56 | 57 | // 查询Dag 58 | // /api/v1/task-scheduler/dag 59 | // /api/v1/task-scheduler/dag?dag_id=delivery_machine&execution_date=2021-04-02 20:12:09.063224 60 | func ListDagRun(c *gin.Context) { 61 | var ( 62 | dagRun scheduler.DagRun 63 | taskInstance scheduler.TaskInstance 64 | ) 65 | // replace 66 | // https://blog.csdn.net/Yvken_Zh/article/details/104861765 67 | c.Request.URL.RawQuery = strings.ReplaceAll(c.Request.URL.RawQuery, "+", "%2b") 68 | 69 | // Get params 70 | dagId := c.Query("dag_id") // ?dag_id=xxx 71 | executionDate := c.Query("execution_date") 72 | fmt.Printf("dagId: %v, executionDate: %v\n", dagId, executionDate) 73 | if dagId != "" && executionDate != "" { 74 | // execute sql 75 | taskInstance.DagId = dagId 76 | taskInstance.ExecutionDate = executionDate 77 | dagRuns, err := taskInstance.ListByDagAndExecDate() 78 | if err != nil { 79 | tools.JSONFailed(c, tools.MSG_ERR, err.Error()) 80 | return 81 | } 82 | // Response 83 | tools.JSONOk(c, dagRuns) 84 | } else { 85 | // execute sql 86 | dagRuns, err := dagRun.List() 87 | if err != nil { 88 | tools.JSONFailed(c, tools.MSG_ERR, err.Error()) 89 | return 90 | } 91 | // Response 92 | tools.JSONOk(c, dagRuns) 93 | } 94 | return 95 | } 96 | 97 | func ListTaskLog(c *gin.Context) { 98 | 99 | } 100 | -------------------------------------------------------------------------------- /internal/app/server.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "fmt" 5 | "lightning-go/internal/db" 6 | "lightning-go/internal/router" 7 | 8 | "github.com/douyu/jupiter" 9 | "github.com/douyu/jupiter/pkg/server/xgin" 10 | "github.com/douyu/jupiter/pkg/util/xcolor" 11 | "github.com/douyu/jupiter/pkg/util/xgo" 12 | "github.com/douyu/jupiter/pkg/worker/xcron" 13 | "github.com/douyu/jupiter/pkg/xlog" 14 | ) 15 | 16 | type Engine struct { 17 | jupiter.Application 18 | } 19 | 20 | func NewEngine() *Engine { 21 | eng := &Engine{} 22 | eng.HideBanner = true 23 | if err := eng.Startup( 24 | xgo.ParallelWithError( 25 | eng.printBanner, 26 | eng.serveHTTP, 27 | eng.initApp, 28 | ), 29 | ); err != nil { 30 | xlog.Panic("startup engine", xlog.Any("err", err)) 31 | } 32 | 33 | return eng 34 | } 35 | 36 | func (eng *Engine) printBanner() error { 37 | 38 | const banner = ` 39 | ______ ____ ____ ____ _____ 40 | / ____// __ \ / __ \ / __ \/ ___/ 41 | / / __ / / / // / / // /_/ /\__ \ 42 | / /_/ // /_/ // /_/ // ____/___/ / 43 | \____/ \____/ \____//_/ /____/ 44 | 45 | Welcome to LIGHTNING-OPS API, starting application ... 46 | ` 47 | 48 | fmt.Println(xcolor.Green(banner)) 49 | return nil 50 | } 51 | func (eng *Engine) initApp() error { 52 | 53 | db.Init() 54 | eng.initCron() 55 | eng.printBanner() 56 | //redis.InitRedis() 57 | return nil 58 | } 59 | 60 | func (eng *Engine) initCron() error { 61 | cron := xcron.StdConfig("chaos").Build() 62 | eng.Schedule(cron) 63 | return nil 64 | } 65 | 66 | //http服务 67 | func (eng *Engine) serveHTTP() error { 68 | server := xgin.StdConfig("http").Build() 69 | router.InitRouters(server) 70 | return eng.Serve(server) 71 | } 72 | -------------------------------------------------------------------------------- /internal/cron/index.go: -------------------------------------------------------------------------------- 1 | package cron 2 | 3 | import ( 4 | "github.com/douyu/jupiter/pkg/worker/xcron" 5 | ) 6 | 7 | func InitShedule(cron *xcron.Cron) { 8 | //add cron task here 9 | 10 | // ex 11 | //if !conf.GetBool("kjcloud.disable_iso") { 12 | // cron.Schedule(xcron.Every(time.Minute*5), xcron.FuncJob(ResetInstallStatus)) 13 | //} 14 | 15 | } 16 | -------------------------------------------------------------------------------- /internal/db/db.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "github.com/douyu/jupiter/pkg/store/gorm" 5 | ) 6 | 7 | var ( 8 | DB *gorm.DB 9 | Airflow *gorm.DB 10 | ) 11 | 12 | func Init() { 13 | 14 | DB = gorm.StdConfig("master").Build() 15 | Airflow = gorm.StdConfig("airflow").Build() 16 | 17 | } 18 | -------------------------------------------------------------------------------- /internal/db/pagination.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import "github.com/douyu/jupiter/pkg/store/gorm" 4 | 5 | // 单表分页 6 | type PaginationQ struct { 7 | Ok bool `json:"ok"` 8 | Limit int `form:"size" json:"size"` 9 | Offset int `form:"page" json:"page"` 10 | Data interface{} `json:"data" comment:"muster be a pointer of slice gorm.Model"` // save pagination list 11 | Total int `json:"total"` 12 | } 13 | 14 | func CRUD(p *PaginationQ, queryTx *gorm.DB, list interface{}) (int, error) { 15 | if p.Limit < 1 { 16 | p.Limit = 10 17 | } 18 | if p.Offset < 1 { 19 | p.Offset = 1 20 | } 21 | 22 | var total int 23 | err := queryTx.Count(&total).Error 24 | if err != nil { 25 | return 0, err 26 | } 27 | offset := p.Limit * (p.Offset - 1) 28 | err = queryTx.Limit(p.Limit).Offset(offset).Find(list).Error 29 | if err != nil { 30 | return 0, err 31 | } 32 | return total, err 33 | } 34 | -------------------------------------------------------------------------------- /internal/models/base.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "bytes" 5 | "database/sql/driver" 6 | "errors" 7 | "fmt" 8 | "lightning-go/pkg/tools" 9 | "time" 10 | ) 11 | 12 | type JSON []byte 13 | 14 | func (j JSON) Value() (driver.Value, error) { 15 | if j.IsNull() { 16 | return nil, nil 17 | } 18 | return string(j), nil 19 | } 20 | func (j *JSON) Scan(value interface{}) error { 21 | if value == nil { 22 | *j = nil 23 | return nil 24 | } 25 | s, ok := value.([]byte) 26 | if !ok { 27 | errors.New("Invalid Scan Source ") 28 | } 29 | *j = append((*j)[0:0], s...) 30 | return nil 31 | } 32 | func (m JSON) MarshalJSON() ([]byte, error) { 33 | if m == nil { 34 | return []byte("null"), nil 35 | } 36 | //fmt.Println("---> MarshalJSON", tools.CompressStr(string(m))) 37 | m = []byte(tools.CompressStr(string(m))) 38 | return []byte(tools.CompressStr(string(m))), nil 39 | //return m, nil 40 | } 41 | func (m *JSON) UnmarshalJSON(data []byte) error { 42 | if m == nil { 43 | return errors.New("null point exception") 44 | } 45 | *m = append((*m)[0:0], data...) 46 | fmt.Println("---> UnmarshalJSON") 47 | return nil 48 | } 49 | func (j JSON) IsNull() bool { 50 | return len(j) == 0 || string(j) == "null" 51 | } 52 | func (j JSON) Equals(j1 JSON) bool { 53 | return bytes.Equal([]byte(j), []byte(j1)) 54 | } 55 | 56 | // 重写MarshalJSON实现models json返回的时间格式 57 | type JSONTime struct { 58 | time.Time 59 | } 60 | 61 | func (t JSONTime) MarshalJSON() ([]byte, error) { 62 | formatted := fmt.Sprintf("\"%s\"", t.Format("2006-01-02 15:04:05")) 63 | return []byte(formatted), nil 64 | } 65 | 66 | func (t JSONTime) Value() (driver.Value, error) { 67 | var zeroTime time.Time 68 | if t.Time.UnixNano() == zeroTime.UnixNano() { 69 | return nil, nil 70 | } 71 | return t.Time, nil 72 | } 73 | 74 | func (t *JSONTime) Scan(v interface{}) error { 75 | value, ok := v.(time.Time) 76 | if ok { 77 | *t = JSONTime{Time: value} 78 | return nil 79 | } 80 | return fmt.Errorf("无法转换 %v 的时间格式", v) 81 | } 82 | -------------------------------------------------------------------------------- /internal/models/multi_cloud/README.md: -------------------------------------------------------------------------------- 1 | ```json5 2 | { 3 | "app_key": "com.pdd.ops.monkey.monkey-cache", 4 | "account": "ali.corp3", 5 | "pay_type": "post_pay", 6 | "region_id": "cn-beijing", 7 | "zone_id": "cn-beijing-a", 8 | "instance_type": "c3.4xlarge", 9 | "hostname": "monkey.monkey-api1.ops.prod", 10 | "is_public": false, 11 | "is_eip": true, 12 | "image_id": "i-xxxxx", 13 | "vpc_id": "vpc-xxx", 14 | "subnet_id": "aaa", 15 | "disks": [{"disk_type":"system", "disk_storage_type":"cloud", "disk_storage_size":100}], 16 | "security_group_ids": ["s1", "s2"] 17 | } 18 | ``` -------------------------------------------------------------------------------- /internal/models/multi_cloud/account.go: -------------------------------------------------------------------------------- 1 | package multi_cloud 2 | 3 | import "lightning-go/internal/db" 4 | 5 | // 创建云主机配置参数 6 | type Account struct { 7 | ID uint `gorm:"primary_key" json:"id"` 8 | EnName string `json:"en_name" binding:"required"` // 英文名 9 | CnName string `json:"cn_name" binding:"required"` // 中文名 10 | Platform string `json:"platform" binding:"required"` // 平台; aws/ali/ten/hw 11 | AccessKeyId string `json:"access_key_id" binding:"required"` // 密钥 12 | SecretKeyId string `json:"secret_key_id" binding:"required"` // 密钥 13 | RootId string `json:"root_id" binding:"omitempty"` // 主帐号ID 14 | } 15 | 16 | func (s *Account) TableName() string { 17 | return "cloud_account" 18 | } 19 | 20 | func (s *Account) Create() (err error) { 21 | err = db.DB.Debug().Table(s.TableName()).Create(s).Error 22 | return 23 | } 24 | 25 | func (s *Account) Delete() (err error) { 26 | err = db.DB.Debug().Table(s.TableName()).Where("ID = ?", s.ID).Delete(&s).Error 27 | return 28 | } 29 | 30 | func (s *Account) Update(data map[string]interface{}) (err error) { 31 | err = db.DB.Debug().Table(s.TableName()).Where("ID = ?", s.ID).Update(data).Error 32 | return 33 | } 34 | 35 | func (s *Account) List() (accounts []Account, err error) { 36 | err = db.DB.Debug().Table(s.TableName()).Select([]string{"id", "en_name", "cn_name", "platform", "access_key_id", "root_id"}).Find(&accounts).Error 37 | return 38 | } 39 | 40 | func (s *Account) GetByAccount() (account Account, err error) { 41 | err = db.DB.Debug().Table(s.TableName()).Where("en_name = ?", s.EnName).Find(&account).Error 42 | return 43 | } 44 | -------------------------------------------------------------------------------- /internal/models/multi_cloud/instance.go: -------------------------------------------------------------------------------- 1 | package multi_cloud 2 | 3 | import ( 4 | "lightning-go/internal/db" 5 | "lightning-go/internal/models" 6 | ) 7 | 8 | // 记录生命周期 9 | type InstanceLifeCycle struct { 10 | InstanceId string `json:"instance_id"` 11 | Uri string `json:"uri"` 12 | Method string `json:"method"` 13 | Query string `json:"query"` 14 | Body models.JSON `json:"body" gorm:"type:text"` 15 | RemoteIp string `json:"remote_ip"` 16 | CreateUser string `json:"create_user"` 17 | Response models.JSON `json:"response" gorm:"type:text"` 18 | IsSuccess bool `json:"is_success"` // 0 fail || 1 success 19 | CreatedAt models.JSONTime `json:"created_at"` 20 | } 21 | 22 | func (s *InstanceLifeCycle) TableName() string { 23 | return "instance_life_cycle" 24 | } 25 | 26 | func (s *InstanceLifeCycle) Create() (err error) { 27 | err = db.DB.Debug().Table(s.TableName()).Create(s).Error 28 | return 29 | } 30 | 31 | func (s *InstanceLifeCycle) GetByInstanceId() (instances []InstanceLifeCycle, err error) { 32 | err = db.DB.Debug().Table(s.TableName()).Where("instance_id = ?", s.InstanceId).Find(&instances).Order("created_at").Error 33 | return 34 | } 35 | -------------------------------------------------------------------------------- /internal/models/multi_cloud/template.go: -------------------------------------------------------------------------------- 1 | package multi_cloud 2 | 3 | import ( 4 | "lightning-go/internal/db" 5 | "lightning-go/internal/models" 6 | 7 | "github.com/jinzhu/gorm" 8 | ) 9 | 10 | // 创建云主机配置参数 11 | type CloudTemplate struct { 12 | gorm.Model 13 | AppKey string `json:"app_key" binding:"required"` // 关联到service_tree服务 14 | Account string `json:"account" binding:"required"` // 帐号 15 | PayType string `json:"pay_type"` // 支付方式 包年和按月 16 | RegionId string `json:"region_id" binding:"required"` // 地域 17 | ZoneId string `json:"zone_id"` // 可用区 18 | InstanceType string `json:"instance_type" binding:"required"` // 机型 19 | IsPublic bool `json:"is_public"` // 公网地址 20 | IsEip bool `json:"is_eip"` // 弹性IP 21 | ImageId string `json:"image_id" binding:"required"` // 镜像ID 22 | VpcId string `json:"vpc_id" binding:"required"` // vpc id 23 | SubnetId string `json:"subnet_id"` // 子网 24 | Disks models.JSON `json:"disks" binding:"required"` // 云盘 25 | SecurityGroupIds models.JSON `json:"security_group_ids" binding:"required"` // 安全组 26 | Count int `json:"-" binding:"omitempty"` // 数量 27 | } 28 | 29 | type Disk struct { 30 | DiskStorageSize int `json:"disk_storage_size"` // 云盘存储大小 31 | DiskStorageType string `json:"disk_storage_type"` // 云盘存储类型 cloud cloud-ssd 32 | DiskType string `json:"disk_type"` // 云盘类型 system | data 33 | } 34 | 35 | func (s *CloudTemplate) TableName() string { 36 | return "cloud_template" 37 | } 38 | 39 | func (s *CloudTemplate) Create() (err error) { 40 | err = db.DB.Debug().Table(s.TableName()).Create(s).Error 41 | return 42 | } 43 | 44 | func (s *CloudTemplate) Get() (template CloudTemplate, err error) { 45 | err = db.DB.Debug().Table(s.TableName()).Where("id = ?", s.ID).Find(&template).Error 46 | return 47 | } 48 | 49 | func (s *CloudTemplate) Delete() (err error) { 50 | err = db.DB.Debug().Table(s.TableName()).Where("id = ?", s.ID).Delete(&s).Error 51 | return 52 | } 53 | 54 | func (s *CloudTemplate) ListByAppKey() (templates []CloudTemplate, err error) { 55 | table := db.DB.Debug().Table(s.TableName()) 56 | table = table.Where("app_key = ?", s.AppKey) 57 | 58 | if s.Account != "" { 59 | table = table.Where("account = ?", s.Account) 60 | } 61 | if s.RegionId != "" { 62 | table = table.Where("region_id = ?", s.RegionId) 63 | } 64 | 65 | err = table.Find(&templates).Error 66 | return 67 | } 68 | -------------------------------------------------------------------------------- /internal/models/scheduler/dag.go: -------------------------------------------------------------------------------- 1 | package scheduler 2 | 3 | import "lightning-go/internal/db" 4 | 5 | // 创建云主机配置参数 6 | type DagRun struct { 7 | ID uint `gorm:"primary_key" json:"id"` 8 | DagId string `json:"dag_id"` 9 | ExecutionDate string `json:"execution_date"` 10 | State string `json:"state"` 11 | RunId string `json:"run_id"` 12 | ExternalTrigger int `json:"external_trigger"` 13 | Conf string `json:"conf"` 14 | StartDate string `json:"start_date"` 15 | EndDate string `json:"end_date"` 16 | RunType string `json:"run_type"` 17 | LastSchedulingDecision string `json:"last_scheduling_decision"` 18 | DagHash string `json:"dag_hash"` 19 | CreatingJobId int `json:"creating_job_id"` 20 | } 21 | 22 | func (s *DagRun) TableName() string { 23 | return "dag_run" 24 | } 25 | 26 | func (s *DagRun) List() (dagRuns []DagRun, err error) { 27 | err = db.Airflow.Debug().Table(s.TableName()).Find(&dagRuns).Error 28 | return 29 | } 30 | -------------------------------------------------------------------------------- /internal/models/scheduler/task_instance.go: -------------------------------------------------------------------------------- 1 | package scheduler 2 | 3 | import "lightning-go/internal/db" 4 | 5 | // 创建云主机配置参数 6 | type TaskInstance struct { 7 | TaskId string `json:"task_id"` 8 | DagId string `json:"dag_id" binding:"required"` 9 | ExecutionDate string `json:"execution_date" binding:"required"` 10 | StartDate string `json:"start_date"` 11 | EndDate string `json:"end_date"` 12 | Duration float64 `json:"duration"` 13 | State string `json:"state"` 14 | TryNumber int `json:"try_number"` 15 | Hostname string `json:"hostname"` 16 | Unixname string `json:"unixname"` 17 | JobId int `json:"job_id"` 18 | Pool string `json:"pool"` 19 | Queue string `json:"queue"` 20 | PriorityWeight string `json:"priority_weight"` 21 | Operator string `json:"operator"` 22 | QueuedDttm string `json:"queued_dttm"` 23 | Pid int `json:"pid"` 24 | MaxTries int `json:"max_tries"` 25 | ExecutorConfig string `json:"executor_config"` 26 | PoolSlots int `json:"pool_slots"` 27 | QueuedByJobId int `json:"queued_by_job_id"` 28 | ExternalExecutorId string `json:"external_executor_id"` 29 | } 30 | 31 | func (s *TaskInstance) TableName() string { 32 | return "task_instance" 33 | } 34 | 35 | func (s *TaskInstance) ListByDagAndExecDate() (tasInstances []TaskInstance, err error) { 36 | table := db.Airflow.Debug().Table(s.TableName()) 37 | err = table.Where("dag_id = ? AND execution_date = ?", s.DagId, s.ExecutionDate).Find(&tasInstances).Error 38 | return 39 | } 40 | -------------------------------------------------------------------------------- /internal/redis/index.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "github.com/douyu/jupiter/pkg/client/redis" 5 | "github.com/douyu/jupiter/pkg/xlog" 6 | "time" 7 | ) 8 | 9 | var RedisStub *redis.Redis 10 | 11 | func InitRedis() error { 12 | 13 | RedisStub = redis.StdRedisStubConfig("kjcloud").Build() 14 | setRes := RedisStub.Set("jupiter-redis", "redisStub", time.Second*5) 15 | xlog.Info("redisStub set string", xlog.Any("res", setRes)) 16 | 17 | getRes := RedisStub.Get("jupiter-redis") 18 | xlog.Info("redisStub get string", xlog.Any("res", getRes)) 19 | return nil 20 | } 21 | -------------------------------------------------------------------------------- /internal/router/middleware/auth.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/douyu/jupiter/pkg/conf" 7 | "github.com/gin-gonic/gin" 8 | "github.com/go-resty/resty/v2" 9 | 10 | "lightning-go/pkg/tools" 11 | ) 12 | 13 | func JWTAuth() gin.HandlerFunc { 14 | return func(c *gin.Context) { 15 | 16 | authSuccess := tools.JSONResult{} 17 | jwt := c.Request.Header.Get("authorization") 18 | // 处理请求 19 | if len(jwt) < 1 { 20 | tools.JSONFailed(c, 403, "The token is empty") 21 | c.Abort() 22 | return 23 | } 24 | 25 | client := resty.New() 26 | _, err := client.R(). 27 | SetHeader("Content-Type", "application/json"). 28 | SetHeader("authorization", jwt). 29 | SetBody(map[string]interface{}{ 30 | "path": c.Request.URL.Path, 31 | "method": c.Request.Method, 32 | }). 33 | SetResult(&authSuccess). 34 | Post(conf.GetString("go-ops.hosts.auth")) 35 | 36 | if err != nil { 37 | tools.JSONFailed(c, tools.MSG_ERR, fmt.Sprintf("auth err :%v ", err)) 38 | c.Abort() 39 | return 40 | } 41 | 42 | if authSuccess.Code != 0 { 43 | tools.JSONFailed(c, authSuccess.Code, authSuccess.Message) 44 | c.Abort() 45 | return 46 | } 47 | c.Next() 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /internal/router/middleware/cors.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | ) 7 | 8 | // 解决跨域 9 | func Cors() gin.HandlerFunc { 10 | return func(c *gin.Context) { 11 | method := c.Request.Method 12 | 13 | c.Header("Access-Control-Allow-Origin", "*") 14 | c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token") 15 | c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS") 16 | c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type") 17 | c.Header("Access-Control-Allow-Credentials", "true") 18 | 19 | if method == "OPTIONS" { 20 | c.AbortWithStatus(http.StatusNoContent) 21 | } 22 | // 处理请求 23 | c.Next() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /internal/router/middleware/life_cycle.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "lightning-go/internal/models/multi_cloud" 9 | "lightning-go/pkg/tools" 10 | "strings" 11 | 12 | "github.com/douyu/jupiter/pkg/xlog" 13 | "github.com/gin-gonic/gin" 14 | "github.com/satori/go.uuid" 15 | ) 16 | 17 | /* 18 | 创建 19 | 变更 20 | 开机 21 | 关机 22 | 重启 23 | 升配 24 | 降配 25 | 下线 26 | */ 27 | 28 | type responseBodyWriter struct { 29 | gin.ResponseWriter 30 | body *bytes.Buffer 31 | } 32 | 33 | func (r responseBodyWriter) Write(b []byte) (int, error) { 34 | r.body.Write(b) 35 | return r.ResponseWriter.Write(b) 36 | } 37 | 38 | func LifeCycle() gin.HandlerFunc { 39 | return func(c *gin.Context) { 40 | 41 | //1. 生成 Request-Id 42 | requestId := c.Request.Header.Get("X-Request-Id") 43 | if requestId == "" { 44 | id, err := uuid.NewV4() 45 | if err != nil { 46 | xlog.Infof("request ID err :%v", err) 47 | return 48 | } 49 | requestId = fmt.Sprintf("%s", id) 50 | } 51 | c.Set("X-Request-Id", requestId) 52 | c.Writer.Header().Set("X-Request-Id", requestId) 53 | 54 | //2. 记录事件生命周期 55 | //cCp := c.Copy() 56 | rawData, _ := c.GetRawData() 57 | // 重新赋值 58 | c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(rawData)) 59 | 60 | cycle := multi_cloud.InstanceLifeCycle{ 61 | Uri: c.Request.URL.Path, 62 | Method: c.Request.Method, 63 | Query: c.Request.URL.RawQuery, 64 | Body: rawData, 65 | RemoteIp: c.ClientIP(), 66 | } 67 | 68 | headerAuthorization := c.Request.Header.Get("Authorization") 69 | if headerAuthorization != "" { 70 | authArr := strings.Split(headerAuthorization, " ") 71 | if len(authArr) == 2 { 72 | cycle.CreateUser = authArr[1] 73 | } 74 | } 75 | 76 | // https://github.com/gin-gonic/gin/issues/1363 77 | w := &responseBodyWriter{body: &bytes.Buffer{}, ResponseWriter: c.Writer} 78 | c.Writer = w 79 | 80 | c.Next() 81 | 82 | // 4. attach field response 83 | cycle.Response = []byte(w.body.String()) 84 | 85 | // 5. attach field IsSuccess 86 | var jsonResult tools.JSONResult 87 | err := json.Unmarshal([]byte(w.body.String()), &jsonResult) 88 | if err == nil { 89 | if jsonResult.Code == tools.MSG_OK { 90 | cycle.IsSuccess = true 91 | if vInstanceInfo, ok := jsonResult.Data.(map[string]interface{}); ok { 92 | cycle.InstanceId = vInstanceInfo["instance_id"].(string) 93 | } else { 94 | bodyInfo, err := tools.StringToMap(rawData) 95 | if err != nil { 96 | xlog.Infof("string to map err :%v", err) 97 | } 98 | if instanceId, ok := bodyInfo["instance_id"].(string); ok { 99 | cycle.InstanceId = instanceId 100 | } 101 | fmt.Println(4) 102 | 103 | } 104 | } 105 | } else { 106 | xlog.Infof("middleware attach instance_id and is_success field err :%v", err) 107 | } 108 | 109 | // 6. save 110 | _ = cycle.Create() 111 | 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /internal/router/middleware/rid.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "fmt" 5 | "github.com/douyu/jupiter/pkg/xlog" 6 | "github.com/gin-gonic/gin" 7 | uuid "github.com/satori/go.uuid" 8 | ) 9 | 10 | func SetRId() gin.HandlerFunc { 11 | return func(c *gin.Context) { 12 | 13 | requestId := c.Request.Header.Get("X-Request-Id") 14 | if requestId == "" { 15 | id, err := uuid.NewV4() 16 | if err != nil { 17 | xlog.Infof("requeset ID err :%v", err) 18 | return 19 | } 20 | requestId = fmt.Sprintf("%s", id) 21 | } 22 | c.Set("X-Request-Id", requestId) 23 | c.Writer.Header().Set("X-Request-Id", requestId) 24 | c.Next() 25 | 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /internal/router/router.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | _ "lightning-go/docs" 5 | "lightning-go/internal/api/multi_cloud" 6 | "lightning-go/internal/api/scheduler" 7 | "lightning-go/internal/router/middleware" 8 | 9 | "github.com/douyu/jupiter/pkg/server/xgin" 10 | "github.com/swaggo/gin-swagger" 11 | "github.com/swaggo/gin-swagger/swaggerFiles" 12 | ) 13 | 14 | func InitRouters(server *xgin.Server) { 15 | 16 | // set Cors middleware 17 | //send.Use(middleware.Cors()) 18 | //send.Use(cors.Default()) 19 | 20 | // set ReQueSetId middleware 21 | //send.Use(middleware.SetRId()) 22 | 23 | // set JWTauth middleware 24 | //send.Use(middleware.JWTAuth()) 25 | 26 | // api swagger doc 27 | url := ginSwagger.URL("http://127.0.0.1:9900/swagger/doc.json") 28 | server.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, url)) 29 | 30 | // task scheduler 31 | ts := server.Group("/api/v1/task-scheduler/dag") 32 | { 33 | ts.POST("/:dagName", scheduler.TriggerDagRun) 34 | ts.GET("/", scheduler.ListDagRun) 35 | } 36 | 37 | // multi-cloud router 38 | template := server.Group("/api/v1/multi-cloud/template") 39 | { 40 | template.POST("", multi_cloud.CreateTemplateView) 41 | template.GET("/", multi_cloud.CetTemplateByAppKeyView) 42 | template.DELETE("/:id", multi_cloud.DeleteTemplateView) 43 | } 44 | instance := server.Group("/api/v1/multi-cloud/instance") 45 | //instance.Use(middleware.LifeCycle()) // 记录生命周期事件 46 | { 47 | instance.POST("/create", middleware.LifeCycle(), multi_cloud.CreateInstanceView) 48 | instance.POST("/start", middleware.LifeCycle(), multi_cloud.StartInstanceView) 49 | instance.POST("/stop", middleware.LifeCycle(), multi_cloud.StopInstanceView) 50 | instance.POST("/reboot", middleware.LifeCycle(), multi_cloud.RebootInstanceView) 51 | instance.POST("/modify_instance_name", middleware.LifeCycle(), multi_cloud.ModifyInstanceNameView) 52 | instance.GET("/", multi_cloud.ListInstancesView) 53 | instance.GET("/:instance_id", multi_cloud.InstanceDetailView) 54 | instance.POST("/destroy", middleware.LifeCycle(), multi_cloud.DestroyInstanceView) 55 | } 56 | cycle := server.Group("/api/v1/multi-cloud/life_cycle") 57 | { 58 | cycle.GET("/:instance_id", multi_cloud.LifeCyclelView) 59 | } 60 | region := server.Group("/api/v1/multi-cloud/regions") 61 | { 62 | region.GET("/", multi_cloud.ListRegionView) 63 | } 64 | account := server.Group("/api/v1/multi-cloud/account") 65 | { 66 | account.POST("", multi_cloud.CreateAccountView) 67 | account.GET("/", multi_cloud.ListAccountView) 68 | account.DELETE("/:id", multi_cloud.DeleteAccountView) 69 | account.PUT("/:id", multi_cloud.UpdateAccountView) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /internal/service/scheduler/serializer.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "lightning-go/pkg/request" 7 | "lightning-go/pkg/tools" 8 | "time" 9 | 10 | "github.com/douyu/jupiter/pkg/conf" 11 | "github.com/tidwall/gjson" 12 | ) 13 | 14 | type CreateInstance struct { 15 | Data map[string]interface{} `json:"data" binding:"required"` 16 | } 17 | 18 | // 验证结构体 19 | type DagRunDataSerializer struct { 20 | DagName string `json:"dag_name"` 21 | Data map[string]interface{} `json:"data" binding:"required"` 22 | } 23 | 24 | type DagRunDataV2Serializer struct { 25 | CreateInstance //匿名字段 26 | } 27 | 28 | type DagRunResponse struct { 29 | DagRunId string `json:"dag_run_id"` 30 | StartDate string `json:"start_date"` 31 | EndDate string `json:"end_date"` 32 | ExecutionDate string `json:"execution_date"` 33 | ExternalTrigger string `json:"external_trigger"` 34 | State string `json:"state"` 35 | } 36 | 37 | /* 38 | 39 | { 40 | "dags": [ 41 | { 42 | "dag_id": "cron_demo", 43 | "description": "A cron demo test", 44 | "file_token": "Ii9yb290L2FpcmZsb3cvZGFncy9jcm9uX2RlbW8ucHki.ZEWrY1Ti5R8nGaywU-AcFmj6ixE", 45 | "fileloc": "/root/airflow/dags/cron_demo.py", 46 | "is_paused": true, 47 | "is_subdag": false, 48 | "owners": [ 49 | "zhengshuai" 50 | ], 51 | "root_dag_id": null, 52 | "schedule_interval": { 53 | "__type": "CronExpression", 54 | "value": "@daily" 55 | }, 56 | "tags": [] 57 | }, 58 | { 59 | "dag_id": "delivery_machine", 60 | "description": "交付机器", 61 | "file_token": "Ii9yb290L2FpcmZsb3cvZGFncy9kZWxpdmVyeV9tYWNoaW5lLnB5Ig.BI52o4f3FdqjUptIKk-4U1Lcc_Y", 62 | "fileloc": "/root/airflow/dags/delivery_machine.py", 63 | "is_paused": false, 64 | "is_subdag": false, 65 | "owners": [ 66 | "zhengshuai" 67 | ], 68 | "root_dag_id": null, 69 | "schedule_interval": null, 70 | "tags": [] 71 | }, 72 | { 73 | "dag_id": "example_bash_operator", 74 | "description": null, 75 | "file_token": ".eJw9yjEOgCAMAMC_sEsHE79DilYgIm1aovJ7Jx0vOQfK3AGL7pXvQO2CWiLI6Jnb7Bew0mkSXA9MZN8DevCUSmHDZD8iWg4spNhZvQz3AmOfI1c.JFGlbq1mSnq865555vKdAlpQONo", 76 | "fileloc": "/root/airflow_env/lib/python3.6/site-packages/airflow/example_dags/example_bash_operator.py", 77 | "is_paused": true, 78 | "is_subdag": false, 79 | "owners": [ 80 | "airflow" 81 | ], 82 | "root_dag_id": null, 83 | "schedule_interval": { 84 | "__type": "CronExpression", 85 | "value": "0 0 * * *" 86 | }, 87 | "tags": [ 88 | { 89 | "name": "example" 90 | }, 91 | { 92 | "name": "example2" 93 | } 94 | ] 95 | }, 96 | */ 97 | 98 | type DagDetailResponse struct { 99 | DagId string `json:"dag_id"` 100 | Description string `json:"description"` 101 | FileToken string `json:"file_token"` 102 | IsPaused bool `json:"is_paused"` 103 | IsSubdag bool `json:"is_subdag"` 104 | RootDagId string `json:"root_dag_id"` 105 | Fileloc string `json:"fileloc"` 106 | Owners []string `json:"owners"` 107 | Tags []map[string]string `json:"tags"` 108 | ScheduleInterval interface{} `json:"schedule_interval"` 109 | } 110 | 111 | type DagListResponse struct { 112 | Dags []DagDetailResponse `json:"dags"` 113 | } 114 | 115 | func GetAirflowHeader() map[string]string { 116 | return map[string]string{ 117 | "Content-Type": "application/json", 118 | "Authorization": fmt.Sprintf("Basic %s", tools.Base64Encode(fmt.Sprintf("%s:%s", 119 | conf.GetString("go-ops.scheduler.airflowUserName"), 120 | conf.GetString("go-ops.scheduler.airflowUserPassword"), 121 | ))), 122 | } 123 | } 124 | 125 | func (s DagRunDataSerializer) Trigger() (interface{}, error) { 126 | var ( 127 | timeout = time.Duration(time.Second * 10) 128 | body = make(map[string]interface{}) 129 | dagResponse DagRunResponse 130 | ) 131 | 132 | dagUrl := fmt.Sprintf("%s/api/v1/dags/%s/dagRuns", 133 | conf.GetString("go-ops.scheduler.airflowUrl"), 134 | s.DagName, 135 | ) 136 | header := map[string]string{ 137 | "Content-Type": "application/json", 138 | "Authorization": fmt.Sprintf("Basic %s", tools.Base64Encode(fmt.Sprintf("%s:%s", 139 | conf.GetString("go-ops.scheduler.airflowUserName"), 140 | conf.GetString("go-ops.scheduler.airflowUserPassword"), 141 | ))), 142 | } 143 | 144 | body["conf"] = s.Data 145 | tools.PrettyPrint(body) 146 | tools.PrettyPrint(dagUrl) 147 | bodyByte, err := tools.JsonToByte(body) 148 | if err != nil { 149 | return "JsonToByte error", err 150 | } 151 | responseByte, err := request.Post(bodyByte, header, dagUrl, timeout) 152 | if err != nil { 153 | return "request.Post err", err 154 | } 155 | 156 | // 反序列化 response 157 | _ = json.Unmarshal(responseByte, &dagResponse) 158 | tools.PrettyPrint(dagResponse) 159 | return dagResponse, nil 160 | } 161 | 162 | type DagMgt struct { 163 | DagName string `json:"dag_name"` 164 | DagRunId string `json:"dag_run_id"` 165 | Limit int `json:"limit"` 166 | } 167 | 168 | func (d DagMgt) ListDag() (DagListResponse, error) { 169 | var ( 170 | dagUrl string 171 | dagListResponse DagListResponse 172 | ) 173 | d.Limit = 100 174 | dagUrl = fmt.Sprintf("%s/api/v1/dags?limit=%d", 175 | conf.GetString("go-ops.scheduler.airflowUrl"), 176 | d.Limit, 177 | ) 178 | 179 | fmt.Printf("dagUrl %s\n", dagUrl) 180 | responseByte, err := request.GetWithHeader(dagUrl, GetAirflowHeader(), time.Duration(time.Second*5)) 181 | if err != nil { 182 | return dagListResponse, err 183 | } 184 | // 反序列化 response 185 | _ = json.Unmarshal(responseByte, &dagListResponse) 186 | tools.PrettyPrint(dagListResponse) 187 | return dagListResponse, nil 188 | } 189 | 190 | func (d DagMgt) DetailDag() (DagDetailResponse, error) { 191 | var ( 192 | dagUrl string 193 | dagDetailResponse DagDetailResponse 194 | ) 195 | dagUrl = fmt.Sprintf("%s/api/v1/dags/%s", 196 | conf.GetString("go-ops.scheduler.airflowUrl"), 197 | d.DagName, 198 | ) 199 | 200 | fmt.Printf("dagUrl %s\n", dagUrl) 201 | responseByte, err := request.GetWithHeader(dagUrl, GetAirflowHeader(), time.Duration(time.Second*5)) 202 | if err != nil { 203 | return dagDetailResponse, err 204 | } 205 | // 反序列化 response 206 | _ = json.Unmarshal(responseByte, &dagDetailResponse) 207 | tools.PrettyPrint(dagDetailResponse) 208 | return dagDetailResponse, nil 209 | } 210 | 211 | func (d DagMgt) TaskInstance() (map[string]interface{}, error) { 212 | var ( 213 | dagUrl string 214 | jsonMap = make(map[string]interface{}) 215 | ) 216 | d.Limit = 100 217 | dagUrl = fmt.Sprintf("%s/api/v1/dags/%s/dagRuns/%s/taskInstances?limit=%d", 218 | conf.GetString("go-ops.scheduler.airflowUrl"), 219 | d.DagName, 220 | d.DagRunId, 221 | d.Limit, 222 | ) 223 | 224 | fmt.Printf("dagUrl %s\n", dagUrl) 225 | responseByte, err := request.GetWithHeader(dagUrl, GetAirflowHeader(), time.Duration(time.Second*5)) 226 | if err != nil { 227 | return jsonMap, err 228 | } 229 | // 反序列化 response 230 | fmt.Println(string(responseByte)) 231 | result := gjson.Get(string(responseByte), "task_instances") 232 | 233 | err = json.Unmarshal([]byte(result.Raw), &jsonMap) 234 | if err != nil { 235 | return jsonMap, err 236 | } 237 | return jsonMap, nil 238 | } 239 | -------------------------------------------------------------------------------- /pkg/multi_cloud_sdk/README.md: -------------------------------------------------------------------------------- 1 | # create instance 2 | 3 | ## ali create instance 4 | ```json 5 | 6 | { 7 | "account": "ali.lightning", 8 | "pay_type": "PostPaid", 9 | "region_id": "cn-beijing", 10 | "zone_id": "cn-beijing-c", 11 | "instance_type": "ecs.sn1.medium", 12 | "image_id": "centos_7_8_x64_20G_alibase_20200914.vhd", 13 | "vpc_id": "vpc-2ze80et76jwcsuc2asobq", 14 | "subnet_id": "vsw-2zelzctt7aougw3bhz5ev", 15 | "disks": [{ 16 | "disk_type": "system", 17 | "disk_storage_type": "cloud", 18 | "disk_storage_size": 100 19 | }], 20 | "security_group_ids": ["sg-2zeb9n0qzygmjwn7hwae"], 21 | "hostname": "lightning-fe1.ops.prod.ali" 22 | } 23 | 24 | ``` 25 | 26 | 27 | ## ten create instance 28 | ```json 29 | 30 | { 31 | "account": "ten.lightning", 32 | "pay_type": "PostPaid", 33 | "region_id": "ap-shanghai", 34 | "zone_id": "ap-shanghai-2", 35 | "instance_type": "S4.MEDIUM4", 36 | "image_id": "img-oikl1tzv", 37 | "vpc_id": "vpc-2qckup44", 38 | "subnet_id": "subnet-r1tor2m1", 39 | "disks": [{ 40 | "disk_type": "system", 41 | "disk_storage_type": "LOCAL_SSD", 42 | "disk_storage_size": 100 43 | }], 44 | "security_group_ids": ["sg-qodmlw0w"], 45 | "hostname": "lightning-fe1.ops.prod.ten" 46 | } 47 | 48 | ``` 49 | -------------------------------------------------------------------------------- /pkg/multi_cloud_sdk/ali.go: -------------------------------------------------------------------------------- 1 | package multi_cloud_sdk 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "lightning-go/pkg/tools" 7 | "strings" 8 | 9 | openapi "github.com/alibabacloud-go/darabonba-openapi/client" 10 | ecs "github.com/alibabacloud-go/ecs-20140526/v2/client" 11 | "github.com/alibabacloud-go/tea/tea" 12 | ) 13 | 14 | type aliyunClient struct { 15 | regionId string 16 | ecsClt *ecs.Client 17 | } 18 | 19 | func NewAliEcs(accessKeyId, accessKeySecret, regionId string) (*aliyunClient, error) { 20 | openCfg := &openapi.Config{ 21 | AccessKeyId: tea.String(accessKeyId), 22 | AccessKeySecret: tea.String(accessKeySecret), 23 | RegionId: tea.String(regionId), 24 | //ConnectTimeout: tea.Int(10), 25 | //ReadTimeout: tea.Int(10), 26 | } 27 | clt, err := ecs.NewClient(openCfg) 28 | if err != nil { 29 | return &aliyunClient{}, err 30 | } 31 | return &aliyunClient{ 32 | regionId: regionId, 33 | ecsClt: clt, 34 | }, nil 35 | } 36 | 37 | // create and start 38 | func (ali *aliyunClient) CreateInstance(PayType, hostname, instanceType, zoneId, imageId, vpcId, subnetId string, securityGroupIds []string, dryRun bool) (instanceInfo map[string]string, err error) { 39 | /* 40 | 实例的付费方式。取值范围: 41 | PrePaid:包年包月 42 | PostPaid(默认):按量付费 43 | 选择包年包月时,您必须确认自己的账号支持余额支付或者信用支付,否则将返回InvalidPayMethod的错误提示。 44 | */ 45 | instanceInfo = make(map[string]string) 46 | runInstancesRequest := &ecs.RunInstancesRequest{ 47 | InstanceChargeType: tea.String(PayType), 48 | InstanceName: tea.String(hostname), 49 | Password: tea.String(password), 50 | InstanceType: tea.String(instanceType), 51 | RegionId: tea.String(ali.regionId), 52 | ZoneId: tea.String(zoneId), 53 | ImageId: tea.String(imageId), 54 | DryRun: tea.Bool(dryRun), 55 | VSwitchId: tea.String(subnetId), 56 | SecurityGroupId: tea.String(securityGroupIds[0]), 57 | } 58 | 59 | if runInstancesRequest.InstanceChargeType == tea.String("PrePaid") { 60 | // 包年包月 特殊设置 61 | runInstancesRequest.PeriodUnit = tea.String("Month") 62 | runInstancesRequest.Period = tea.Int32(1) 63 | runInstancesRequest.AutoRenew = tea.Bool(true) 64 | runInstancesRequest.AutoRenewPeriod = tea.Int32(1) 65 | } 66 | response, err := ali.ecsClt.RunInstances(runInstancesRequest) 67 | if err != nil { 68 | return instanceInfo, err 69 | } 70 | instanceIdSet := response.Body.InstanceIdSets.InstanceIdSet 71 | if len(instanceIdSet) == 0 { 72 | return instanceInfo, errors.New("Request ok, but instance create not success. ") 73 | } 74 | instanceInfo["instance_id"] = tea.StringValue(instanceIdSet[0]) 75 | return instanceInfo, nil 76 | } 77 | 78 | // create 79 | func (ali *aliyunClient) CreateInstanceButStop(PayType, hostname, instanceType, zoneId, imageId, vpcId, subnetId string, securityGroupIds []string, dryRun bool) (instanceId string, err error) { 80 | /* 81 | 实例的付费方式。取值范围: 82 | PrePaid:包年包月 83 | PostPaid(默认):按量付费 84 | 选择包年包月时,您必须确认自己的账号支持余额支付或者信用支付,否则将返回InvalidPayMethod的错误提示。 85 | */ 86 | request := &ecs.CreateInstanceRequest{ 87 | InstanceChargeType: tea.String(PayType), 88 | InstanceName: tea.String(hostname), 89 | Password: tea.String(password), 90 | InstanceType: tea.String(instanceType), 91 | RegionId: tea.String(ali.regionId), 92 | ZoneId: tea.String(zoneId), 93 | ImageId: tea.String(imageId), 94 | DryRun: tea.Bool(dryRun), 95 | VSwitchId: tea.String(subnetId), 96 | SecurityGroupId: tea.String(securityGroupIds[0]), 97 | } 98 | 99 | if request.InstanceChargeType == tea.String("PrePaid") { 100 | // 包年包月 特殊设置 101 | request.PeriodUnit = tea.String("Month") 102 | request.Period = tea.Int32(1) 103 | request.AutoRenew = tea.Bool(true) 104 | request.AutoRenewPeriod = tea.Int32(1) 105 | } 106 | response, err := ali.ecsClt.CreateInstance(request) 107 | if err != nil { 108 | return "Call RunInstances error", err 109 | } 110 | 111 | return tea.StringValue(response.Body.InstanceId), nil 112 | } 113 | 114 | func (ali *aliyunClient) StartInstance(instanceId string) (string, error) { 115 | startInstancesRequest := &ecs.StartInstancesRequest{ 116 | RegionId: tea.String(ali.regionId), 117 | BatchOptimization: tea.String("AllTogether"), 118 | InstanceId: []*string{tea.String(instanceId)}, 119 | } 120 | response, err := ali.ecsClt.StartInstances(startInstancesRequest) 121 | if err != nil { 122 | return err.Error(), err 123 | } 124 | for _, v := range response.Body.InstanceResponses.InstanceResponse { 125 | if *v.Message == "success" && *v.Code == "200" { 126 | return *response.Body.RequestId, nil 127 | } 128 | } 129 | return "", errors.New("Start fail. ") 130 | } 131 | 132 | func (ali *aliyunClient) StopInstance(instanceId string) (string, error) { 133 | stopInstancesRequest := &ecs.StopInstancesRequest{ 134 | RegionId: tea.String(ali.regionId), 135 | BatchOptimization: tea.String("AllTogether"), 136 | InstanceId: []*string{tea.String(instanceId)}, 137 | ForceStop: tea.Bool(true), 138 | } 139 | response, err := ali.ecsClt.StopInstances(stopInstancesRequest) 140 | if err != nil { 141 | return err.Error(), err 142 | } 143 | 144 | for _, v := range response.Body.InstanceResponses.InstanceResponse { 145 | if *v.Message == "success" && *v.Code == "200" { 146 | return *response.Body.RequestId, nil 147 | } 148 | } 149 | return "", errors.New("Stop fail. ") 150 | } 151 | 152 | func (ali *aliyunClient) RebootInstance(instanceId string, forceStop bool) (string, error) { 153 | // 如果机器状态是关机,则无法重启;必须先开机 在关机才行 154 | rebootInstanceRequest := &ecs.RebootInstanceRequest{ 155 | InstanceId: tea.String(instanceId), 156 | ForceStop: tea.Bool(forceStop), 157 | } 158 | response, err := ali.ecsClt.RebootInstance(rebootInstanceRequest) 159 | if err != nil { 160 | return err.Error(), err 161 | } 162 | requestId := response.Body.RequestId 163 | return *requestId, nil 164 | } 165 | 166 | func (ali *aliyunClient) DestroyInstance(instanceId string, forceStop bool) (string, error) { 167 | deleteInstanceRequest := &ecs.DeleteInstanceRequest{ 168 | InstanceId: tea.String(instanceId), 169 | Force: tea.Bool(forceStop), 170 | TerminateSubscription: tea.Bool(true), // 是否强制释放运行中(Running的实例 171 | } 172 | response, err := ali.ecsClt.DeleteInstance(deleteInstanceRequest) 173 | if err != nil { 174 | return err.Error(), err 175 | } 176 | return *response.Body.RequestId, nil 177 | } 178 | 179 | func (ali *aliyunClient) ModifyInstanceName(instanceId string, instanceName string) (string, error) { 180 | modifyInstanceAttributeRequest := &ecs.ModifyInstanceAttributeRequest{ 181 | InstanceId: tea.String(instanceId), 182 | HostName: tea.String(instanceName), 183 | InstanceName: tea.String(instanceName), 184 | } 185 | response, err := ali.ecsClt.ModifyInstanceAttribute(modifyInstanceAttributeRequest) 186 | if err != nil { 187 | return err.Error(), err 188 | } 189 | return *response.Body.RequestId, nil 190 | } 191 | 192 | func (ali *aliyunClient) ListInstance(instanceId string) (map[string]interface{}, error) { 193 | var instanceInfo = make(map[string]interface{}) 194 | describeInstancesRequest := &ecs.DescribeInstancesRequest{ 195 | PageSize: tea.Int32(100), 196 | InstanceIds: tea.String(instanceId), 197 | RegionId: tea.String(ali.regionId), 198 | DryRun: tea.Bool(false), 199 | } 200 | instances, err := ali.ecsClt.DescribeInstances(describeInstancesRequest) 201 | if err != nil { 202 | return instanceInfo, err 203 | } 204 | if len(instances.Body.Instances.Instance) == 0 { 205 | return instanceInfo, errors.New(fmt.Sprintf("%s not found ", instanceId)) 206 | } 207 | for _, instance := range instances.Body.Instances.Instance { 208 | instanceInfo, _ = ali.processInstance(instance) 209 | 210 | } 211 | return instanceInfo, nil 212 | } 213 | 214 | func (ali *aliyunClient) ListInstances() ([]map[string]interface{}, error) { 215 | var instanceArray []map[string]interface{} 216 | for pageNumber := 1; pageNumber <= 3; pageNumber++ { 217 | describeInstancesRequest := &ecs.DescribeInstancesRequest{ 218 | PageSize: tea.Int32(100), 219 | PageNumber: tea.Int32(int32(pageNumber)), 220 | RegionId: tea.String(ali.regionId), 221 | DryRun: tea.Bool(false), 222 | } 223 | // 复制代码运行请自行打印 API 的返回值 224 | instances, err := ali.ecsClt.DescribeInstances(describeInstancesRequest) 225 | if err != nil { 226 | return instanceArray, err 227 | } 228 | // 如果返回结果集中为空 则退出循环 229 | if len(instances.Body.Instances.Instance) == 0 { 230 | break 231 | } 232 | for _, instance := range instances.Body.Instances.Instance { 233 | info, _ := ali.processInstance(instance) 234 | instanceArray = append(instanceArray, info) 235 | 236 | } 237 | } 238 | return instanceArray, nil 239 | } 240 | 241 | func (ali *aliyunClient) processInstance(instance *ecs.DescribeInstancesResponseBodyInstancesInstance) (map[string]interface{}, error) { 242 | defer func() { 243 | if err := recover(); err != nil { 244 | fmt.Printf("recover %s\n", err) 245 | } 246 | }() 247 | 248 | tools.PrettyPrint(instance) 249 | 250 | var publicIpAddress *string 251 | info := make(map[string]interface{}) 252 | 253 | if len(instance.PublicIpAddress.IpAddress) != 0 { 254 | publicIpAddress = instance.PublicIpAddress.IpAddress[0] 255 | } 256 | info = map[string]interface{}{ 257 | "type": cloudType, 258 | "platform": "ali", 259 | "instance_charge_type": instance.InstanceChargeType, 260 | "instance_id": instance.InstanceId, 261 | "private_ip": instance.VpcAttributes.PrivateIpAddress.IpAddress[0], 262 | "public_ip": publicIpAddress, 263 | "eip_ip": instance.EipAddress.IpAddress, 264 | //"instance_name": instance.InstanceName, 265 | "hostname": instance.InstanceName, 266 | "image_id": instance.ImageId, 267 | "os_system": instance.OSType, 268 | "os_version": instance.OSName, 269 | "instance_type": instance.InstanceType, 270 | "region_id": instance.RegionId, 271 | "zone_id": instance.ZoneId, 272 | "vpc_id": instance.VpcAttributes.VpcId, 273 | "subnet_id": instance.VpcAttributes.VSwitchId, 274 | "security_group_ids": instance.SecurityGroupIds.SecurityGroupId, 275 | "state": strings.ToLower(*instance.Status), 276 | "mem_total": *instance.Memory / 1024, 277 | "cpu_total": instance.Cpu, 278 | "start_time": tools.ReplaceDateTime(*instance.StartTime), 279 | "create_server_time": tools.ReplaceDateTime(*instance.CreationTime), 280 | "expired_time": tools.ReplaceDateTime(*instance.ExpiredTime), 281 | } 282 | return info, nil 283 | } 284 | 285 | func (ali *aliyunClient) ListRegions() ([]map[string]string, error) { 286 | var regions []map[string]string 287 | describeRegionsRequest := &ecs.DescribeRegionsRequest{ 288 | AcceptLanguage: tea.String("zh-CN"), 289 | ResourceType: tea.String("instance"), 290 | } 291 | // 复制代码运行请自行打印 API 的返回值 292 | regionInfo, err := ali.ecsClt.DescribeRegions(describeRegionsRequest) 293 | if err != nil { 294 | return regions, err 295 | } 296 | for _, reg := range regionInfo.Body.Regions.Region { 297 | info := map[string]string{ 298 | "region_id": *reg.RegionId, 299 | "local_name": *reg.LocalName, 300 | } 301 | regions = append(regions, info) 302 | } 303 | return regions, nil 304 | } 305 | -------------------------------------------------------------------------------- /pkg/multi_cloud_sdk/aws.go: -------------------------------------------------------------------------------- 1 | package multi_cloud_sdk 2 | -------------------------------------------------------------------------------- /pkg/multi_cloud_sdk/core.go: -------------------------------------------------------------------------------- 1 | package multi_cloud_sdk 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "lightning-go/internal/models/multi_cloud" 7 | ) 8 | 9 | type client interface { 10 | CreateInstance(PayType, hostname, instanceType, zoneId, imageId, vpcId, subnetId string, securityGroupIds []string, dryRun bool) (map[string]string, error) 11 | StartInstance(string) (string, error) 12 | StopInstance(string) (string, error) 13 | RebootInstance(string, bool) (string, error) 14 | DestroyInstance(string, bool) (string, error) 15 | ModifyInstanceName(string, string) (string, error) 16 | ListInstances() ([]map[string]interface{}, error) 17 | ListInstance(string) (map[string]interface{}, error) 18 | ListRegions() ([]map[string]string, error) 19 | } 20 | 21 | func NewFactory(cloudPlatform, accessKeyId, secretKeyId, regionId string) (clt client, err error) { 22 | switch cloudPlatform { 23 | case "ali": 24 | clt, err = NewAliEcs(accessKeyId, secretKeyId, regionId) 25 | return 26 | case "ten": 27 | clt, err = NewTenCvm(accessKeyId, secretKeyId, regionId) 28 | return 29 | } 30 | return 31 | } 32 | 33 | func NewFactoryByAccount(account, regionId string) (clt client, err error) { 34 | var ( 35 | mc = multi_cloud.Account{EnName: account} 36 | ) 37 | accessKeyInfo, err := mc.GetByAccount() 38 | if err != nil { 39 | return nil, errors.New(fmt.Sprintf("account: %s not found. ", account)) 40 | } 41 | switch accessKeyInfo.Platform { 42 | case "ali": 43 | clt, err = NewAliEcs(accessKeyInfo.AccessKeyId, accessKeyInfo.SecretKeyId, regionId) 44 | return 45 | case "ten": 46 | clt, err = NewTenCvm(accessKeyInfo.AccessKeyId, accessKeyInfo.SecretKeyId, regionId) 47 | return 48 | } 49 | return 50 | } 51 | -------------------------------------------------------------------------------- /pkg/multi_cloud_sdk/gmap.go: -------------------------------------------------------------------------------- 1 | package multi_cloud_sdk 2 | 3 | var ( 4 | password string = "Zys$$2123ju" 5 | cloudType string = "cloud" 6 | ) 7 | -------------------------------------------------------------------------------- /pkg/multi_cloud_sdk/hw.go: -------------------------------------------------------------------------------- 1 | package multi_cloud_sdk 2 | -------------------------------------------------------------------------------- /pkg/multi_cloud_sdk/ten.go: -------------------------------------------------------------------------------- 1 | package multi_cloud_sdk 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "lightning-go/pkg/tools" 7 | "strings" 8 | 9 | "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" 10 | 11 | "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" 12 | cvm "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312" 13 | ) 14 | 15 | type tencentcloudClient struct { 16 | regionId string 17 | cvmClt *cvm.Client 18 | } 19 | 20 | func NewTenCvm(accessKeyId, accessKeySecret, regionId string) (*tencentcloudClient, error) { 21 | credential := common.NewCredential( 22 | accessKeyId, 23 | accessKeySecret, 24 | ) 25 | cpf := profile.NewClientProfile() 26 | cpf.HttpProfile.Endpoint = "cvm.tencentcloudapi.com" 27 | clt, err := cvm.NewClient(credential, regionId, cpf) 28 | if err != nil { 29 | return &tencentcloudClient{}, errors.New("") 30 | } 31 | return &tencentcloudClient{ 32 | regionId: regionId, 33 | cvmClt: clt, 34 | }, nil 35 | } 36 | 37 | func (ten *tencentcloudClient) CreateInstance(instanceChargeType, hostname, instanceType, zoneId, imageId, vpcId, subnetId string, securityGroupIds []string, dryRun bool) (instanceInfo map[string]string, err error) { 38 | //fmt.Println(hostname, instanceType, zoneId, imageId, dryRun) 39 | /* 40 | instanceChargeType 实例付费类型 41 | PREPAID:预付费,即包年包月 42 | POSTPAID_BY_HOUR:按小时后付费 43 | CDHPAID:独享子机(基于专用宿主机创建,宿主机部分的资源不收费) 44 | SPOTPAID:竞价付费 45 | */ 46 | instanceInfo = make(map[string]string) 47 | request := cvm.NewRunInstancesRequest() 48 | 49 | if instanceChargeType == "PostPaid" { 50 | request.InstanceChargeType = common.StringPtr("POSTPAID_BY_HOUR") 51 | } else if instanceChargeType == "PrePaid" { 52 | request.InstanceChargeType = common.StringPtr("PREPAID") 53 | request.InstanceChargePrepaid = &cvm.InstanceChargePrepaid{ 54 | Period: common.Int64Ptr(1), 55 | RenewFlag: common.StringPtr("NOTIFY_AND_AUTO_RENEW"), 56 | } 57 | } else { 58 | return instanceInfo, errors.New("instanceChargeType only support PostPaid or PrePaid") 59 | } 60 | request.InstanceType = common.StringPtr(instanceType) 61 | request.Placement = &cvm.Placement{ 62 | Zone: common.StringPtr(zoneId), 63 | } 64 | request.InstanceCount = common.Int64Ptr(1) 65 | request.InstanceName = common.StringPtr(hostname) 66 | request.LoginSettings = &cvm.LoginSettings{ 67 | Password: common.StringPtr(password), 68 | } 69 | request.VirtualPrivateCloud = &cvm.VirtualPrivateCloud{ 70 | VpcId: common.StringPtr(vpcId), 71 | SubnetId: common.StringPtr(subnetId), 72 | } 73 | request.HostName = common.StringPtr(hostname) 74 | request.ImageId = common.StringPtr(imageId) 75 | request.SecurityGroupIds = common.StringPtrs(securityGroupIds) 76 | request.SystemDisk = &cvm.SystemDisk{ 77 | DiskType: common.StringPtr("CLOUD_PREMIUM"), 78 | DiskSize: common.Int64Ptr(100), 79 | } 80 | //request.DataDisks = []*cvm.DataDisk { 81 | // &cvm.DataDisk { 82 | // DiskType: common.StringPtr("LOCAL_SSD"), 83 | // DiskSize: common.Int64Ptr(100), 84 | // DeleteWithInstance: common.BoolPtr(true), 85 | // }, 86 | //} 87 | request.DryRun = common.BoolPtr(dryRun) 88 | 89 | response, err := ten.cvmClt.RunInstances(request) 90 | if err != nil { 91 | return instanceInfo, err 92 | } 93 | fmt.Printf("%s", response.ToJsonString()) 94 | fmt.Println(response.Response.InstanceIdSet) 95 | 96 | instanceIdSet := response.Response.InstanceIdSet 97 | //requestId := response.Response.RequestId 98 | if len(instanceIdSet) != 1 { 99 | return instanceInfo, err 100 | } else { 101 | instanceInfo["instance_id"] = common.StringValues(instanceIdSet)[0] 102 | return instanceInfo, nil 103 | } 104 | } 105 | 106 | func (ten *tencentcloudClient) StartInstance(instanceId string) (string, error) { 107 | request := cvm.NewStartInstancesRequest() 108 | 109 | request.InstanceIds = common.StringPtrs([]string{instanceId}) 110 | 111 | response, err := ten.cvmClt.StartInstances(request) 112 | if err != nil { 113 | return "An API error", err 114 | } 115 | return *response.Response.RequestId, nil 116 | } 117 | 118 | func (ten *tencentcloudClient) StopInstance(instanceId string) (string, error) { 119 | request := cvm.NewStopInstancesRequest() 120 | 121 | request.InstanceIds = common.StringPtrs([]string{instanceId}) 122 | request.ForceStop = common.BoolPtr(true) 123 | //request.StopType = common.StringPtr("HARD") 124 | request.StoppedMode = common.StringPtr("STOP_CHARGING") 125 | 126 | response, err := ten.cvmClt.StopInstances(request) 127 | if err != nil { 128 | return "An API error", err 129 | } 130 | return *response.Response.RequestId, nil 131 | } 132 | 133 | func (ten *tencentcloudClient) RebootInstance(instanceId string, forceStop bool) (string, error) { 134 | request := cvm.NewRebootInstancesRequest() 135 | 136 | request.InstanceIds = common.StringPtrs([]string{instanceId}) 137 | //request.ForceReboot = common.BoolPtr(true) 138 | if forceStop { 139 | request.StopType = common.StringPtr("HARD") 140 | } else { 141 | request.StopType = common.StringPtr("SOFT") 142 | } 143 | 144 | response, err := ten.cvmClt.RebootInstances(request) 145 | if err != nil { 146 | return "An API error", err 147 | } 148 | return *response.Response.RequestId, nil 149 | } 150 | 151 | func (ten *tencentcloudClient) DestroyInstance(instanceId string, forceStop bool) (string, error) { 152 | request := cvm.NewTerminateInstancesRequest() 153 | 154 | request.InstanceIds = common.StringPtrs([]string{instanceId}) 155 | 156 | response, err := ten.cvmClt.TerminateInstances(request) 157 | if err != nil { 158 | return "An API error", err 159 | } 160 | return *response.Response.RequestId, nil 161 | } 162 | 163 | func (ten *tencentcloudClient) ModifyInstanceName(instanceId string, instanceName string) (string, error) { 164 | request := cvm.NewModifyInstancesAttributeRequest() 165 | 166 | request.InstanceIds = common.StringPtrs([]string{instanceId}) 167 | request.InstanceName = common.StringPtr(instanceName) 168 | 169 | response, err := ten.cvmClt.ModifyInstancesAttribute(request) 170 | if err != nil { 171 | return "An API error", err 172 | } 173 | return *response.Response.RequestId, nil 174 | } 175 | 176 | func (ten *tencentcloudClient) ModifyInstanceSecurityGroups(instanceId string, securityGroupIds []string) (string, error) { 177 | request := cvm.NewModifyInstancesAttributeRequest() 178 | 179 | request.InstanceIds = common.StringPtrs([]string{instanceId}) 180 | request.SecurityGroups = common.StringPtrs(securityGroupIds) 181 | 182 | response, err := ten.cvmClt.ModifyInstancesAttribute(request) 183 | if err != nil { 184 | return "An API error", err 185 | } 186 | fmt.Printf("%s", response.ToJsonString()) 187 | return "", nil 188 | } 189 | 190 | func (ten *tencentcloudClient) ResetInstance() ([]map[string]string, error) { 191 | return nil, nil 192 | } 193 | 194 | func (ten *tencentcloudClient) ListInstance(instanceId string) (map[string]interface{}, error) { 195 | var instanceInfo = make(map[string]interface{}) 196 | request := cvm.NewDescribeInstancesRequest() 197 | request.InstanceIds = common.StringPtrs([]string{instanceId}) 198 | response, err := ten.cvmClt.DescribeInstances(request) 199 | if err != nil { 200 | return instanceInfo, err 201 | } 202 | // 如果返回结果集中为空 则退出循环 203 | if len(response.Response.InstanceSet) == 0 { 204 | return instanceInfo, errors.New("Response instanceSet count 0 ") 205 | } 206 | info, err := ten.processInstance(response.Response.InstanceSet[0]) 207 | if err != nil { 208 | return instanceInfo, err 209 | } 210 | instanceInfo = info 211 | return instanceInfo, nil 212 | } 213 | 214 | func (ten *tencentcloudClient) ListInstances() ([]map[string]interface{}, error) { 215 | var instanceArray []map[string]interface{} 216 | for pageNumber := 0; pageNumber <= 3; pageNumber++ { 217 | request := cvm.NewDescribeInstancesRequest() 218 | request.Offset = common.Int64Ptr(int64(pageNumber) * 100) 219 | request.Limit = common.Int64Ptr(100) 220 | response, err := ten.cvmClt.DescribeInstances(request) 221 | if err != nil { 222 | return instanceArray, err 223 | } 224 | // 如果返回结果集中为空 则退出循环 225 | if len(response.Response.InstanceSet) == 0 { 226 | break 227 | } 228 | for _, instance := range response.Response.InstanceSet { 229 | info, err := ten.processInstance(instance) 230 | if err == nil { 231 | instanceArray = append(instanceArray, info) 232 | } 233 | } 234 | } 235 | tools.PrettyPrint(instanceArray) 236 | return instanceArray, nil 237 | } 238 | 239 | func (ten *tencentcloudClient) processInstance(instance *cvm.Instance) (map[string]interface{}, error) { 240 | var ( 241 | publicIP string 242 | expiredTime string 243 | instanceChargeType string 244 | ) 245 | 246 | defer func() { 247 | if err := recover(); err != nil { 248 | fmt.Printf("recover %s\n", err) 249 | } 250 | }() 251 | tools.PrettyPrint(instance) 252 | if *instance.InstanceChargeType == "PREPAID" { 253 | instanceChargeType = "PrePaid" 254 | expiredTime = tools.ReplaceDateTime(*instance.ExpiredTime) 255 | } else { 256 | // POSTPAID_BY_HOUR 257 | instanceChargeType = "PostPaid" 258 | expiredTime = "0000-00-00 00:00:00" 259 | } 260 | // system and data disk 261 | disks := []map[string]interface{}{} 262 | tmpSystemDisk := make(map[string]interface{}) 263 | tmpSystemDisk["disk_id"] = *instance.SystemDisk.DiskId 264 | tmpSystemDisk["disk_type"] = *instance.SystemDisk.DiskType 265 | tmpSystemDisk["disk_size"] = *instance.SystemDisk.DiskSize 266 | disks = append(disks, tmpSystemDisk) 267 | if len(instance.DataDisks) >= 1 { 268 | for _, disk := range instance.DataDisks { 269 | tmpDataDisk := make(map[string]interface{}) 270 | tmpDataDisk["disk_id"] = *disk.DiskId 271 | tmpDataDisk["disk_type"] = *disk.DiskType 272 | tmpDataDisk["disk_size"] = *disk.DiskSize 273 | disks = append(disks, tmpDataDisk) 274 | } 275 | } 276 | 277 | // publicIpx 278 | if len(instance.PublicIpAddresses) == 1 { 279 | publicIP = *instance.PublicIpAddresses[0] 280 | } else { 281 | publicIP = "" 282 | } 283 | return map[string]interface{}{ 284 | "type": cloudType, 285 | "platform": "ten", 286 | "instance_charge_type": instanceChargeType, 287 | "instance_id": instance.InstanceId, 288 | "private_ip": instance.PrivateIpAddresses[0], 289 | "public_ip": publicIP, 290 | "eip_ip": "", 291 | "hostname": instance.InstanceName, 292 | "image_id": instance.ImageId, 293 | "os_system": strings.Fields(*instance.OsName)[0], 294 | "os_version": instance.OsName, 295 | "instance_type": instance.InstanceType, 296 | "region_id": ten.regionId, 297 | "zone_id": instance.Placement.Zone, 298 | "vpc_id": instance.VirtualPrivateCloud.VpcId, 299 | "subnet_id": instance.VirtualPrivateCloud.SubnetId, 300 | "security_group_ids": instance.SecurityGroupIds, 301 | "state": strings.ToLower(*instance.InstanceState), 302 | "mem_total": *instance.Memory, 303 | "cpu_total": instance.CPU, 304 | "disks": disks, 305 | "start_time": "0000-00-00 00:00:00", 306 | "create_server_time": tools.ReplaceDateTime(*instance.CreatedTime), 307 | "expired_time": expiredTime, 308 | }, nil 309 | } 310 | 311 | func (ten *tencentcloudClient) ListRegions() (regions []map[string]string, err error) { 312 | request := cvm.NewDescribeRegionsRequest() 313 | response, err := ten.cvmClt.DescribeRegions(request) 314 | if err != nil { 315 | return regions, err 316 | } 317 | for _, v := range response.Response.RegionSet { 318 | m := make(map[string]string) 319 | m["region_id"] = *v.Region 320 | m["local_name"] = *v.RegionName 321 | regions = append(regions, m) 322 | } 323 | return regions, nil 324 | } 325 | -------------------------------------------------------------------------------- /pkg/request/request.go: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "time" 9 | ) 10 | 11 | func Post(body []byte, header map[string]string, url string, timeout time.Duration) ([]byte, error) { 12 | req, err := http.NewRequest("POST", url, bytes.NewBuffer(body)) 13 | if err != nil { 14 | return nil, err 15 | } 16 | for key, value := range header { 17 | req.Header.Set(key, value) 18 | } 19 | client := &http.Client{} 20 | if timeout != 0 { 21 | client.Timeout = timeout 22 | } 23 | resp, err := client.Do(req) 24 | if err != nil { 25 | return nil, err 26 | } 27 | defer resp.Body.Close() 28 | response, _ := ioutil.ReadAll(resp.Body) 29 | if resp.StatusCode != http.StatusOK { 30 | return nil, fmt.Errorf("remote server occer error and http code is %d, response: %v", resp.StatusCode, string(response)) 31 | } 32 | //response, _ := ioutil.ReadAll(resp.Body) 33 | return response, nil 34 | } 35 | 36 | func Delete(url string, timeout time.Duration) ([]byte, error) { 37 | req, err := http.NewRequest("DELETE", url, nil) 38 | if err != nil { 39 | return nil, err 40 | } 41 | client := &http.Client{} 42 | if timeout != 0 { 43 | client.Timeout = timeout 44 | } 45 | resp, err := client.Do(req) 46 | if err != nil { 47 | return nil, err 48 | } 49 | defer resp.Body.Close() 50 | if resp.StatusCode != http.StatusOK { 51 | return nil, fmt.Errorf("remote server occer error and http code is %d", resp.StatusCode) 52 | } 53 | response, _ := ioutil.ReadAll(resp.Body) 54 | return response, nil 55 | } 56 | func Get(url string, timeout time.Duration) ([]byte, error) { 57 | req, err := http.NewRequest("GET", url, nil) 58 | if err != nil { 59 | return nil, err 60 | } 61 | client := &http.Client{} 62 | if timeout != 0 { 63 | client.Timeout = timeout 64 | } 65 | resp, err := client.Do(req) 66 | if err != nil { 67 | return nil, err 68 | } 69 | defer resp.Body.Close() 70 | if resp.StatusCode != http.StatusOK { 71 | return nil, fmt.Errorf("remote server occer error and http code is %d", resp.StatusCode) 72 | } 73 | response, _ := ioutil.ReadAll(resp.Body) 74 | return response, nil 75 | } 76 | 77 | func GetWithHeader(url string, header map[string]string, timeout time.Duration) ([]byte, error) { 78 | req, err := http.NewRequest("GET", url, nil) 79 | if err != nil { 80 | return nil, err 81 | } 82 | for key, value := range header { 83 | req.Header.Set(key, value) 84 | } 85 | client := &http.Client{} 86 | if timeout != 0 { 87 | client.Timeout = timeout 88 | } 89 | resp, err := client.Do(req) 90 | if err != nil { 91 | return nil, err 92 | } 93 | defer resp.Body.Close() 94 | if resp.StatusCode != http.StatusOK { 95 | return nil, fmt.Errorf("remote server occer error and http code is %d", resp.StatusCode) 96 | } 97 | response, _ := ioutil.ReadAll(resp.Body) 98 | return response, nil 99 | } 100 | 101 | func Put(body []byte, header map[string]string, url string, timeout time.Duration) ([]byte, error) { 102 | req, err := http.NewRequest("PUT", url, bytes.NewBuffer(body)) 103 | if err != nil { 104 | return nil, err 105 | } 106 | for key, value := range header { 107 | req.Header.Set(key, value) 108 | } 109 | client := &http.Client{} 110 | if timeout != 0 { 111 | client.Timeout = timeout 112 | } 113 | resp, err := client.Do(req) 114 | if err != nil { 115 | return nil, err 116 | } 117 | defer resp.Body.Close() 118 | response, _ := ioutil.ReadAll(resp.Body) 119 | if resp.StatusCode != http.StatusOK { 120 | return nil, fmt.Errorf("remote server occer error and http code is %d, response: %v", resp.StatusCode, string(response)) 121 | } 122 | return response, nil 123 | } -------------------------------------------------------------------------------- /pkg/tools/convert.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "encoding/json" 5 | "regexp" 6 | "strconv" 7 | "strings" 8 | "time" 9 | 10 | "github.com/fatih/structs" 11 | ) 12 | 13 | // string to int 14 | func StringToInt(s string) (int, error) { 15 | return strconv.Atoi(s) 16 | } 17 | 18 | // string to uint 19 | func StringToUint(s string) (v uint, err error) { 20 | var _int int 21 | _int, err = strconv.Atoi(s) 22 | v = uint(_int) 23 | return 24 | } 25 | 26 | func StructToMap(s interface{}) (m map[string]interface{}) { 27 | m = structs.Map(s) 28 | return 29 | } 30 | 31 | func StringToMap(s []byte) (m map[string]interface{}, err error) { 32 | m = make(map[string]interface{}) 33 | err = json.Unmarshal(s, &m) 34 | return 35 | } 36 | 37 | func ByteToJson(b []byte) (data map[string]interface{}, err error) { 38 | err = json.Unmarshal(b, &data) 39 | return 40 | } 41 | 42 | func JsonToByte(data map[string]interface{}) (b []byte, err error) { 43 | b, err = json.Marshal(data) 44 | return 45 | } 46 | 47 | func ReplaceDateTime(dTime string) string { 48 | // "2020-11-02T15:38Z" 49 | s := strings.Replace(dTime, "T", " ", -1) 50 | return strings.Trim(strings.Replace(s, "Z", " ", -1), " ") 51 | } 52 | 53 | //利用正则表达式压缩字符串,去除空格或制表符 54 | func CompressStr(str string) string { 55 | if str == "" { 56 | return "" 57 | } 58 | //匹配一个或多个空白符的正则表达式 59 | reg := regexp.MustCompile("\\s+") 60 | return reg.ReplaceAllString(str, "") 61 | } 62 | 63 | func FormatNormalDateTime(dateTime time.Time) string { 64 | return dateTime.Format("2006-01-02 15:04:05") 65 | } 66 | -------------------------------------------------------------------------------- /pkg/tools/hash.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import "encoding/base64" 4 | 5 | func Base64Encode(s string) string { 6 | return base64.StdEncoding.EncodeToString([]byte(s)) 7 | } 8 | -------------------------------------------------------------------------------- /pkg/tools/msg.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/gin-gonic/gin" 7 | "net/http" 8 | ) 9 | 10 | const ( 11 | MSG_ERR = -1 12 | MSG_OK = 0 13 | UNAUTHORIZED_ACCESS = 10000 14 | ) 15 | 16 | // JSONResult json 17 | type JSONResult struct { 18 | Code int `json:"code"` 19 | Message string `json:"message"` 20 | Data interface{} `json:"data"` 21 | RequestId interface{} `json:"request_id"` 22 | } 23 | 24 | type JSONResultQ struct { 25 | Code int `json:"code"` 26 | Message string `json:"message"` 27 | Data interface{} `json:"data"` 28 | Total int `json:"total"` 29 | RequestId interface{} `json:"request_id"` 30 | } 31 | 32 | // JSON 渲染 33 | func JSON(c *gin.Context, Code int, message string, data ...interface{}) { 34 | result := new(JSONResult) 35 | result.Code = Code 36 | result.Message = message 37 | 38 | if len(data) > 0 { 39 | result.Data = data[0] 40 | } else { 41 | result.Data = "" 42 | } 43 | c.JSON(http.StatusOK, result) 44 | } 45 | 46 | // Json 失败返回 47 | func JSONFailed(c *gin.Context, Code int, message string) { 48 | result := new(JSONResult) 49 | result.Code = Code 50 | result.Message = message 51 | 52 | c.JSON(http.StatusOK, result) 53 | } 54 | 55 | // Json 成功返回 56 | func JSONOk(c *gin.Context, data ...interface{}) { 57 | var result interface{} 58 | var message string 59 | fmt.Println(data, len(data)) 60 | if len(data) > 0 && len(data) <= 1 { 61 | result = data[0] 62 | } else if len(data) > 1 { 63 | v, ok := data[1].(string) 64 | if ok { 65 | message = v 66 | } else { 67 | message = "" 68 | } 69 | } else { 70 | result = "" 71 | } 72 | 73 | c.JSON(http.StatusOK, JSONResult{ 74 | Code: MSG_OK, 75 | Message: message, 76 | Data: result, 77 | RequestId: c.Request.Header.Get("X-Request-Id"), 78 | }) 79 | } 80 | 81 | func JSONokQ(c *gin.Context, total int, data ...interface{}) { 82 | var result interface{} 83 | if len(data) > 0 { 84 | result = data[0] 85 | } else { 86 | result = "" 87 | } 88 | 89 | c.JSON(http.StatusOK, JSONResultQ{ 90 | Code: MSG_OK, 91 | Message: "Ok", 92 | Data: result, 93 | Total: total, 94 | RequestId: c.Request.Header.Get("X-Request-Id"), 95 | }) 96 | } 97 | 98 | type Err struct { 99 | Code int `json:"code"` 100 | Message string `json:"message"` 101 | } 102 | 103 | func (e *Err) Error() string { 104 | err, _ := json.Marshal(e) 105 | return string(err) 106 | } 107 | 108 | func New(code int, msg string) *Err { 109 | return &Err{ 110 | Code: code, 111 | Message: msg, 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /pkg/tools/print.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | ) 7 | 8 | // 格式化打印 9 | func PrettyPrint(v interface{}) { 10 | b, err := json.MarshalIndent(v, "", " ") 11 | if err == nil { 12 | fmt.Println(string(b)) 13 | } else { 14 | fmt.Println(b) 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /pkg/三方调用: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengyansheng/lightning-go/4f66fb5cf55e4c989392a550f14df926b4be93d5/pkg/三方调用 -------------------------------------------------------------------------------- /scripts/cron/cron_sync_instance.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "lightning-go/pkg/request" 7 | "lightning-go/pkg/tools" 8 | "strings" 9 | "sync" 10 | "time" 11 | 12 | "github.com/tidwall/gjson" 13 | ) 14 | 15 | var ( 16 | multiCloudHost = "http://go-ops.aiops724.com" 17 | opsHost = "http://ops.aiops724.com" 18 | regionUri = "/api/v1/multi-cloud/regions/" 19 | instanceUri = "/api/v1/multi-cloud/instance/" 20 | cmdbUri = "/api/v1/cmdb/instances/" 21 | cmdbUpdateUri = "/api/v1/cmdb/instances/multi_update/" 22 | accounts = []string{ 23 | "ali.lightning", 24 | "ten.lightning", 25 | } 26 | ) 27 | 28 | func getCloudPlatRegions(account string) (m []map[string]string, err error) { 29 | var regionId string 30 | accountArray := strings.Split(account, ".") 31 | switch accountArray[0] { 32 | case "ali": 33 | regionId = "cn-beijing" 34 | case "ten": 35 | regionId = "ap-beijing" 36 | case "aws": 37 | regionId = "cn-north-1" 38 | } 39 | url := fmt.Sprintf("%s%s?account=%s®ion_id=%s", multiCloudHost, regionUri, account, regionId) 40 | dataByte, err := request.Get(url, time.Duration(time.Second*10)) 41 | if err != nil { 42 | return 43 | } 44 | 45 | if gjson.Get(string(dataByte), "code").Num == 0 { 46 | dataArray := gjson.Get(string(dataByte), "data").Array() 47 | for _, region := range dataArray { 48 | regionMap := make(map[string]string) 49 | regionMap["local_name"] = region.Map()["local_name"].Str 50 | regionMap["region_id"] = region.Map()["region_id"].Str 51 | m = append(m, regionMap) 52 | } 53 | return 54 | } 55 | return 56 | } 57 | 58 | type response struct { 59 | Code int `json:"code"` 60 | Message string `json:"message"` 61 | Data []map[string]interface{} `json:"data"` 62 | } 63 | 64 | func getInstances(account, regionId string) (m []map[string]interface{}, err error) { 65 | url := fmt.Sprintf("%s%s?account=%s®ion_id=%s", multiCloudHost, instanceUri, account, regionId) 66 | dataByte, err := request.Get(url, time.Duration(time.Second*10)) 67 | if err != nil { 68 | return 69 | } 70 | 71 | var res response 72 | err = json.Unmarshal(dataByte, &res) 73 | m = res.Data 74 | return 75 | } 76 | 77 | func postCmdb(instances map[string]interface{}) (string, error) { 78 | url := fmt.Sprintf("%s%s", opsHost, cmdbUri) 79 | fmt.Printf("url:%s\n", url) 80 | headers := make(map[string]string) 81 | headers["Content-Type"] = "application/json" 82 | dataByte, err := json.Marshal(instances) 83 | if err != nil { 84 | return "json.Marshal err", err 85 | } 86 | dataByte, err = request.Post(dataByte, headers, url, time.Duration(time.Second*10)) 87 | if err != nil { 88 | return string(dataByte), err 89 | } 90 | return "", nil 91 | } 92 | 93 | func postMultiCmdb(instances []map[string]interface{}) (string, error) { 94 | url := fmt.Sprintf("%s%s", opsHost, cmdbUri) 95 | fmt.Printf("url:%s\n", url) 96 | headers := make(map[string]string) 97 | headers["Content-Type"] = "application/json" 98 | dataByte, err := json.Marshal(instances) 99 | if err != nil { 100 | return "json.Marshal err", err 101 | } 102 | dataByte, err = request.Post(dataByte, headers, url, time.Duration(time.Second*10)) 103 | if err != nil { 104 | return string(dataByte), err 105 | } 106 | return "", nil 107 | } 108 | 109 | func updateCmdb(instances []map[string]interface{}) (string, error) { 110 | url := fmt.Sprintf("%s%s", opsHost, cmdbUpdateUri) 111 | fmt.Printf("url:%s\n", url) 112 | headers := make(map[string]string) 113 | headers["Content-Type"] = "application/json" 114 | dataByte, err := json.Marshal(instances) 115 | if err != nil { 116 | return "json.Marshal err", err 117 | } 118 | dataByte, err = request.Put(dataByte, headers, url, time.Duration(time.Second*10)) 119 | if err != nil { 120 | return string(dataByte), err 121 | } 122 | return "", nil 123 | } 124 | 125 | func main() { 126 | var wg sync.WaitGroup 127 | for _, account := range accounts { 128 | //1. 获取平台下所有的地域 129 | regionMap, err := getCloudPlatRegions(account) 130 | if err != nil { 131 | fmt.Println("getCloudPlatRegions err", err) 132 | return 133 | } 134 | 135 | //2. 获取云主机信息 136 | for _, info := range regionMap { 137 | wg.Add(1) 138 | go func(account, regionId string) { 139 | defer wg.Done() 140 | instances, err := getInstances(account, regionId) 141 | if err != nil { 142 | fmt.Println("getInstances err", err) 143 | return 144 | } 145 | if len(instances) == 0 { 146 | fmt.Printf("account: %s region_id: %s instances not found.\n", account, regionId) 147 | return 148 | } 149 | fmt.Printf("account: %s region_id: %s instances length %d.\n", account, regionId, len(instances)) 150 | var dataList []map[string]interface{} 151 | for _, instanceInfo := range instances { 152 | instanceInfo["account"] = account 153 | dataList = append(dataList, instanceInfo) 154 | } 155 | tools.PrettyPrint(dataList) 156 | //3. 同步云主机信息 157 | res, err := postMultiCmdb(dataList) 158 | if err != nil { 159 | fmt.Printf("Post cmdb err: %v, response: %v\n", err, res) 160 | } 161 | //4. 变更云主机信息到cmdb 162 | res, err = updateCmdb(dataList) 163 | if err != nil { 164 | fmt.Printf("Update cmdb err: %v, response: %v\n", err, res) 165 | } 166 | }(account, info["region_id"]) 167 | } 168 | } 169 | wg.Wait() 170 | fmt.Println("cron sync instance ok.") 171 | 172 | } 173 | -------------------------------------------------------------------------------- /scripts/业务脚本: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengyansheng/lightning-go/4f66fb5cf55e4c989392a550f14df926b4be93d5/scripts/业务脚本 --------------------------------------------------------------------------------