diff --git a/api.go b/api.go index 6f68418..171f14e 100644 --- a/api.go +++ b/api.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "git.icechen.cn/pkg/wujian_develop_tool/config" "git.icechen.cn/pkg/wujian_develop_tool/util" "os" @@ -17,11 +18,11 @@ import ( // ActionListApi API应用列表 func ActionListApi(c *cli.Context) error { - if err := Init(c); err != nil { + if err := config.Init(c); err != nil { return err } color.Green("API应用列表:") - for _, api := range config.HasApi { + for _, api := range config.Config.HasApi { color.Green("\t%s", api.Name) color.Green("\t\t路径:\t%s", api.Root) color.Green("\t\t类型:\t%s", api.Type) @@ -40,7 +41,7 @@ func ActionListApi(c *cli.Context) error { // ActionNewApi 创建API应用 func ActionNewApi(c *cli.Context) error { - if err := Init(c); err != nil { + if err := config.Init(c); err != nil { return err } @@ -59,8 +60,8 @@ func ActionNewApi(c *cli.Context) error { } for _, newApp := range appList { color.Green("正在创建[%s]API应用...", newApp) - app := config.Api.GetApp(newApp) - app = Api{ + app := config.Config.Api.GetApp(newApp) + app = config.Api{ Name: newApp, Root: "app/api/" + newApp, } @@ -95,7 +96,7 @@ func ActionNewApi(c *cli.Context) error { app.Host = util.ReadLineWithMessage("请输入API应用请求的域名(host,如:api.seamlesser.com,建议为空): ") app.Path = util.ReadLineWithMessage("请输入API应用请求的路由(path,如:/uwe/core/?(.*),建议为空): ") - err = deploy.GenDeploy(config.Name, newApp, app.AliasName) + err = deploy.GenDeploy(config.Config.Name, app) if err != nil { return err } @@ -105,7 +106,7 @@ func ActionNewApi(c *cli.Context) error { return err } - err = config.AppendAPI(app) + err = config.Config.AppendAPI(app) if err != nil { return err } @@ -117,5 +118,6 @@ func ActionNewApi(c *cli.Context) error { // ActionFixApi 修复API应用 func ActionFixApi(c *cli.Context) error { + color.Red("开发中...") return nil } diff --git a/ci.go b/ci.go index 78e6469..0a615fa 100644 --- a/ci.go +++ b/ci.go @@ -2,6 +2,7 @@ package main import ( "context" + "git.icechen.cn/pkg/wujian_develop_tool/config" "os" "strings" @@ -13,14 +14,13 @@ import ( ) var ( - ciHost = "https://ci.icechen.cn" - // ciToken = "b3e73c2e2f405e65de484d37ea48bb26" - ciToken = "XRFPl4YgUk8ZT3q5D2dLzqNDUNhPuwCf" + ciHost = "https://ci.icechen.cn" + ciToken = "b3e73c2e2f405e65de484d37ea48bb26" ) // ActionBuildCI ci创建新构建 func ActionBuildCI(c *cli.Context) error { - if err := Init(c); err != nil { + if err := config.Init(c); err != nil { return err } @@ -43,15 +43,15 @@ func ActionBuildCI(c *cli.Context) error { temp := strings.Split(fInfo, string(os.PathSeparator)) name := temp[len(temp)-1] - color.Red("创建构建的仓库: %s/%s@master", config.Name, name) + color.Red("创建构建的仓库: %s/%s@master", config.Config.Name, name) if len(app) > 0 { color.Red("强制构建的应用(除了最后一次提交或指定的commit提交本应构建的应用): %+v\n\n", app) } - b, err := client.BuildCreate(config.Name, name, commit, "master", map[string]string{"app": app}) + b, err := client.BuildCreate(config.Config.Name, name, commit, "master", map[string]string{"app": app}) if err != nil { return err } - color.Green("构建成功!\n构建ID: %d\n去看看: %s/%s/%s/%d", b.Number, ciHost, config.Name, name, b.Number) + color.Green("构建成功!\n构建ID: %d\n去看看: %s/%s/%s/%d", b.Number, ciHost, config.Config.Name, name, b.Number) return nil } diff --git a/configFile.go b/config/configFile.go similarity index 91% rename from configFile.go rename to config/configFile.go index 9441a89..eb8db83 100644 --- a/configFile.go +++ b/config/configFile.go @@ -1,4 +1,4 @@ -package main +package config import ( "git.icechen.cn/pkg/wujian_develop_tool/util" @@ -22,13 +22,13 @@ func Init(c *cli.Context) error { } appList := c.Args().Slice() - config.HasApi = config.Api.HasApp(appList) - config.HasService = config.Service.HasApp(appList) + Config.HasApi = Config.Api.HasApp(appList) + Config.HasService = Config.Service.HasApp(appList) return nil } -type Config struct { +type ConfigFile struct { Kind string `json:"kind" yaml:"kind"` Type string `json:"type" yaml:"type"` Name string `json:"name" yaml:"name"` @@ -63,21 +63,21 @@ type ( } ) -var config Config +var Config ConfigFile func ReadConfig() error { configFile, err := ioutil.ReadFile(DefaultConfigFile) if err != nil { return err } - err = yaml.Unmarshal(configFile, &config) + err = yaml.Unmarshal(configFile, &Config) if err != nil { return err } return nil } -func (c *Config) AppendAPI(api Api) error { +func (c *ConfigFile) AppendAPI(api Api) error { has := false for i, a := range c.Api { if a.Name == api.Name { @@ -99,7 +99,7 @@ func (c *Config) AppendAPI(api Api) error { return util.WriteFileWithNotEdit(DefaultConfigFile, out) } -func (c *Config) AppendService(service Service) error { +func (c *ConfigFile) AppendService(service Service) error { has := false for i, s := range c.Service { if s.Name == service.Name { diff --git a/health.go b/health.go index 361dc13..af4bc43 100644 --- a/health.go +++ b/health.go @@ -2,6 +2,7 @@ package main import ( "bytes" + "git.icechen.cn/pkg/wujian_develop_tool/config" "git.icechen.cn/pkg/wujian_develop_tool/util" ) @@ -33,7 +34,7 @@ func (hr healthResp) String() string { return b.String() } -func healthApi(api Api) healthResp { +func healthApi(api config.Api) healthResp { ret := healthResp{ Dir: false, Deploy: false, @@ -57,3 +58,28 @@ func healthApi(api Api) healthResp { return ret } + +func healthService(service config.Service) healthResp { + ret := healthResp{ + Dir: false, + Deploy: false, + Dockerfile: false, + } + + // Dir 健康度 + if util.ExistDir(service.Root) { + ret.Dir = true + } + + // Deploy 健康度 + if util.ExistDir(service.Root + "/deploy") { + ret.Deploy = true + } + + // Dockerfile 健康度 + if util.ExistFile(service.Root + "/Dockerfile") { + ret.Dockerfile = true + } + + return ret +} diff --git a/main.go b/main.go index 27374a6..9a354cd 100644 --- a/main.go +++ b/main.go @@ -30,7 +30,7 @@ func main() { app = &cli.App{ Name: "wdt", Usage: "无间开发者工具箱", - Version: "v0.0.1", + Version: "v1.0.0", EnableBashCompletion: true, Flags: flags, Commands: cli.Commands{ @@ -67,17 +67,17 @@ func main() { { Name: "new", Usage: "创建一个service应用", - Action: ActionNewApi, + Action: ActionNewService, }, { Name: "list", Usage: "service应用列表", - Action: ActionListApi, + Action: ActionListService, }, { Name: "fix", Usage: "service应用修复", - Action: ActionFixApi, + Action: ActionFixService, }, }, }, diff --git a/service.go b/service.go new file mode 100644 index 0000000..a77cdba --- /dev/null +++ b/service.go @@ -0,0 +1,121 @@ +package main + +import ( + "fmt" + "git.icechen.cn/pkg/wujian_develop_tool/config" + "git.icechen.cn/pkg/wujian_develop_tool/util" + "os" + + "git.icechen.cn/pkg/wujian_develop_tool/template/service/deploy" + + "git.icechen.cn/pkg/wujian_develop_tool/template/service/docker/golang" + + "github.com/fatih/color" + + _ "embed" + "github.com/urfave/cli/v2" +) + +// ActionListService Service应用列表 +func ActionListService(c *cli.Context) error { + if err := config.Init(c); err != nil { + return err + } + color.Green("Service应用列表:") + for _, service := range config.Config.HasService { + color.Green("\t%s", service.Name) + color.Green("\t\t路径:\t%s", service.Root) + color.Green("\t\t类型:\t%s", service.Type) + color.Green("\t\t端口:\t%s", service.Port) + c := color.New(color.Bold, color.FgRed) + c.EnableColor() + _, err := c.Println(healthService(service)) + if err != nil { + return err + } + + fmt.Print("\n") + } + return nil +} + +// ActionNewService 创建Service应用 +func ActionNewService(c *cli.Context) error { + if err := config.Init(c); err != nil { + return err + } + + appList := c.Args().Slice() + if len(appList) == 0 { + appName := "" + for { + appName = util.ReadLineWithMessage("请输入要创建的api名称: ") + if appName != "" { + break + } else { + color.Red("应用名称不能为空!") + } + } + appList = append(appList, appName) + } + for _, newApp := range appList { + color.Green("正在创建[%s]API应用...", newApp) + app := config.Config.Service.GetApp(newApp) + app = config.Service{ + Name: newApp, + Root: "app/api/" + newApp, + } + + if util.ExistDir(app.Root) && !util.ReadBoolWithMessage(app.Root+"目录已存在,是否要覆盖app文件目录(y/n): ") { + return nil + } + + err := os.MkdirAll(app.Root, os.ModePerm) + if err != nil { + return err + } + + app.AliasName = util.ReadLineWithMessage("请输入应用的简短描述(别名): ") + for { + app.Type = util.ReadLineWithMessage("请输入应用的类型(默认golang): ") + if app.Type == "golang" || app.Type == "" { + app.Type = "golang" + break + } else { + color.Red("暂不支持: [%s]类型", app.Type) + } + } + for { + app.Port = util.ReadLineWithMessage("请输入应用暴露的端口: ") + if app.Port != "" { + break + } else { + color.Red("端口号不能为空!") + } + } + + err = deploy.GenDeploy(config.Config.Name, app) + if err != nil { + return err + } + + err = golang.GenDockerfile(newApp) + if err != nil { + return err + } + + err = config.Config.AppendService(app) + if err != nil { + return err + } + + color.Green("[%s]API应用创建成功!!!\n\n", newApp) + } + return nil +} + +// ActionFixService 修复Service应用 +func ActionFixService(c *cli.Context) error { + color.Red("开发中...") + return nil +} diff --git a/template/api/deploy/deoloy.go b/template/api/deploy/deoloy.go index 84eb054..02d2dd6 100644 --- a/template/api/deploy/deoloy.go +++ b/template/api/deploy/deoloy.go @@ -3,6 +3,8 @@ package deploy import ( "embed" _ "embed" + "fmt" + "git.icechen.cn/pkg/wujian_develop_tool/config" "os" "github.com/fatih/color" @@ -13,24 +15,40 @@ import ( //go:embed "*" var deployDir embed.FS -func GenDeploy(namespace string, name string, aliasName string) error { +func GenDeploy(namespace string, api config.Api) error { color.Green("正在生成deploy...") // deploy 文件夹 - deployDirPath := "app/api/" + name + "/deploy" + deployDirPath := api.Root + "/deploy" err := os.MkdirAll(deployDirPath, os.ModePerm) if err != nil { return err } + host := api.Host + if host == "" { + host = "api.seamlesser.com" + } + + path := api.Path + if path == "" { + path = fmt.Sprintf("/%s/%s/?(.*)", namespace, api.Name) + } + + data := map[string]string{ + "NameSpace": namespace, + "AppName": api.Name, + "AliasName": api.AliasName, + "Port": api.Port, + "Host": host, + "Path": path} + // value.yaml valuesTemplate, err := deployDir.ReadFile("values.tpl") if err != nil { return err } - err = util.TemplateToFile(deployDirPath+"/values.yaml", - string(valuesTemplate), - map[string]string{"NameSpace": namespace, "AppName": name, "AliasName": aliasName}) + err = util.TemplateToFile(deployDirPath+"/values.yaml", string(valuesTemplate), data) if err != nil { return err } @@ -40,15 +58,13 @@ func GenDeploy(namespace string, name string, aliasName string) error { if err != nil { return err } - err = util.TemplateToFile(deployDirPath+"/Chart.yaml", - string(chartTemplate), - map[string]string{"NameSpace": namespace, "AppName": name, "AliasName": aliasName}) + err = util.TemplateToFile(deployDirPath+"/Chart.yaml", string(chartTemplate), data) if err != nil { return err } // templates 文件夹 - templatesDirPath := "app/api/" + name + "/deploy/templates" + templatesDirPath := api.Root + "/deploy/templates" err = os.MkdirAll(templatesDirPath, os.ModePerm) if err != nil { return err diff --git a/template/api/deploy/values.tpl b/template/api/deploy/values.tpl index ee15846..6b2e6c3 100644 --- a/template/api/deploy/values.tpl +++ b/template/api/deploy/values.tpl @@ -2,6 +2,6 @@ nameSpace: {{ .NameSpace }} aliasName: {{ .AliasName }} image: reg.icechen.cn/{{ .NameSpace }}/api-{{ .AppName }} imageTag: latest -port: 8080 -host: api.seamlesser.com -path: /{{ .NameSpace }}/{{ .AppName }}/?(.*) \ No newline at end of file +port: {{ .Port }} +host: {{ .Host }} +path: {{ .Path }} \ No newline at end of file diff --git a/template/service/deploy/.helmignore b/template/service/deploy/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/template/service/deploy/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/template/service/deploy/Chart.tpl b/template/service/deploy/Chart.tpl new file mode 100644 index 0000000..7a413f1 --- /dev/null +++ b/template/service/deploy/Chart.tpl @@ -0,0 +1,24 @@ +apiVersion: v2 +name: {{ .AppName }} +description: {{ .AliasName }} + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.0.1 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "0.0.1" diff --git a/template/service/deploy/deoloy.go b/template/service/deploy/deoloy.go new file mode 100644 index 0000000..8be2441 --- /dev/null +++ b/template/service/deploy/deoloy.go @@ -0,0 +1,107 @@ +package deploy + +import ( + "embed" + _ "embed" + "git.icechen.cn/pkg/wujian_develop_tool/config" + "os" + + "github.com/fatih/color" + + "git.icechen.cn/pkg/wujian_develop_tool/util" +) + +//go:embed "*" +var deployDir embed.FS + +func GenDeploy(namespace string, service config.Service) error { + color.Green("正在生成deploy...") + + // deploy 文件夹 + deployDirPath := service.Root + "/deploy" + err := os.MkdirAll(deployDirPath, os.ModePerm) + if err != nil { + return err + } + + data := map[string]string{ + "NameSpace": namespace, + "AppName": service.Name, + "AliasName": service.AliasName, + "Port": service.Port} + + // value.yaml + valuesTemplate, err := deployDir.ReadFile("values.tpl") + if err != nil { + return err + } + err = util.TemplateToFile(deployDirPath+"/values.yaml", string(valuesTemplate), data) + if err != nil { + return err + } + + // Chart.yaml + chartTemplate, err := deployDir.ReadFile("Chart.tpl") + if err != nil { + return err + } + err = util.TemplateToFile(deployDirPath+"/Chart.yaml", string(chartTemplate), data) + if err != nil { + return err + } + + // templates 文件夹 + templatesDirPath := service.Root + "/deploy/templates" + err = os.MkdirAll(templatesDirPath, os.ModePerm) + if err != nil { + return err + } + + // .helmignore 文件 + err = copyTo(".helmignore", deployDirPath+"/.helmignore") + if err != nil { + return err + } + + // _helpers.tpl 文件 + err = copyTo("templates/helpers.tpl", templatesDirPath+"/_helpers.tpl") + if err != nil { + return err + } + + // deployment.yaml 文件 + err = copyTo("templates/deployment.yaml", templatesDirPath+"/deployment.yaml") + if err != nil { + return err + } + + // NOTES.txt 文件 + err = copyTo("templates/NOTES.txt", templatesDirPath+"/NOTES.txt") + if err != nil { + return err + } + + // service.yaml 文件 + err = copyTo("templates/service.yaml", templatesDirPath+"/service.yaml") + if err != nil { + return err + } + + return nil +} + +func copyTo(fileName string, toPath string) error { + fileContent, err := deployDir.ReadFile(fileName) + if err != nil { + return err + } + + toFile, err := os.OpenFile(toPath, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, os.ModePerm) + if err != nil { + return err + } + defer toFile.Close() + + _, err = toFile.Write(fileContent) + return err +} diff --git a/template/service/deploy/templates/NOTES.txt b/template/service/deploy/templates/NOTES.txt new file mode 100644 index 0000000..3041ade --- /dev/null +++ b/template/service/deploy/templates/NOTES.txt @@ -0,0 +1 @@ +notes \ No newline at end of file diff --git a/template/service/deploy/templates/deployment.yaml b/template/service/deploy/templates/deployment.yaml new file mode 100644 index 0000000..e990c24 --- /dev/null +++ b/template/service/deploy/templates/deployment.yaml @@ -0,0 +1,62 @@ +kind: Deployment +apiVersion: apps/v1 +metadata: + name: {{ .Release.Name }} + namespace: {{ .Values.nameSpace }} + labels: + app: {{ .Release.Name }} + annotations: + kubesphere.io/alias-name: {{ .Values.aliasName }} + kubesphere.io/creator: drone +spec: + replicas: 1 + selector: + matchLabels: + app: {{ .Release.Name }} + template: + metadata: + labels: + app: {{ .Release.Name }} + spec: + volumes: + - name: host-time + hostPath: + path: /etc/localtime + type: '' + containers: + - name: {{ .Release.Name }} + image: {{ .Values.image }}:{{ .Values.imageTag }} + ports: + - containerPort: {{ .Values.port }} + protocol: TCP + env: + - name: endpoints + value: 'etcd:2379' + resources: + limits: + cpu: 200m + memory: 1000Mi + requests: + cpu: 10m + memory: 200Mi + volumeMounts: + - name: host-time + readOnly: true + mountPath: /etc/localtime + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + imagePullPolicy: IfNotPresent + restartPolicy: Always + terminationGracePeriodSeconds: 30 + dnsPolicy: ClusterFirst + securityContext: {} + imagePullSecrets: + - name: registry-secret + schedulerName: default-scheduler + strategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 25% + maxSurge: 25% + revisionHistoryLimit: 10 + progressDeadlineSeconds: 600 \ No newline at end of file diff --git a/template/service/deploy/templates/helpers.tpl b/template/service/deploy/templates/helpers.tpl new file mode 100644 index 0000000..e69de29 diff --git a/template/service/deploy/templates/service.yaml b/template/service/deploy/templates/service.yaml new file mode 100644 index 0000000..fbf4aeb --- /dev/null +++ b/template/service/deploy/templates/service.yaml @@ -0,0 +1,22 @@ +kind: Service +apiVersion: v1 +metadata: + name: {{ .Release.Name }} + namespace: {{ .Values.nameSpace }} + annotations: + kubesphere.io/creator: drone + labels: + app: {{ .Release.Name }} +spec: + ports: + - name: grpc-{{ .Release.Name }} + protocol: TCP + port: 80 + targetPort: {{ .Values.port }} + selector: + app: {{ .Release.Name }} + type: ClusterIP + sessionAffinity: None + ipFamilies: + - IPv4 + ipFamilyPolicy: SingleStack \ No newline at end of file diff --git a/template/service/deploy/values.tpl b/template/service/deploy/values.tpl new file mode 100644 index 0000000..e0800f2 --- /dev/null +++ b/template/service/deploy/values.tpl @@ -0,0 +1,5 @@ +nameSpace: {{ .NameSpace }} +aliasName: {{ .AliasName }} +image: reg.icechen.cn/{{ .NameSpace }}/api-{{ .AppName }} +imageTag: latest +port: {{ .Port }} \ No newline at end of file diff --git a/template/service/docker/golang/Dockerfile.tpl b/template/service/docker/golang/Dockerfile.tpl new file mode 100644 index 0000000..c42976c --- /dev/null +++ b/template/service/docker/golang/Dockerfile.tpl @@ -0,0 +1,20 @@ +FROM golang:1.17 as builder +ENV GO111MODULE on +ENV GOPROXY https://goproxy.io,direct +WORKDIR /go/cache +ADD go.mod . +ADD go.sum . +RUN go mod download +WORKDIR /go/src +ADD . . +RUN go mod tidy +RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o service_{{ .Name }} ./app/service/{{ .Name }} + +FROM reg.icechen.cn/alpine as {{ .Name }} +WORKDIR /go/src +COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo +COPY --from=builder /go/src/service_{{ .Name }} ./ +ENV TZ=Asia/Shanghai +RUN chmod +x ./service_{{ .Name }} +EXPOSE 8080 +CMD ["./service_{{ .Name }}"] \ No newline at end of file diff --git a/template/service/docker/golang/golang.go b/template/service/docker/golang/golang.go new file mode 100644 index 0000000..262e0a2 --- /dev/null +++ b/template/service/docker/golang/golang.go @@ -0,0 +1,24 @@ +package golang + +import ( + _ "embed" + + "github.com/fatih/color" + + "git.icechen.cn/pkg/wujian_develop_tool/util" +) + +//go:embed "Dockerfile.tpl" +var dockerfileTemplate string + +//go:embed "main.tpl" +var mainGoTemplate string + +func GenDockerfile(name string) error { + color.Green("正在生成dockerfile...") + err := util.TemplateToFile("app/service/"+name+"/Dockerfile", dockerfileTemplate, map[string]string{"Name": name}) + if err != nil { + return err + } + return util.TemplateToFile("app/service/"+name+"/main.go", mainGoTemplate, nil) +} diff --git a/template/service/docker/golang/main.tpl b/template/service/docker/golang/main.tpl new file mode 100644 index 0000000..21ec4e8 --- /dev/null +++ b/template/service/docker/golang/main.tpl @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func main() { + fmt.Println("我是个示例") +}