mirror of
https://github.com/usual2970/certimate.git
synced 2025-10-05 14:04:54 +00:00
Add qiniu deployer
This commit is contained in:
@@ -10,16 +10,13 @@ import (
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
)
|
||||
|
||||
const (
|
||||
configTypeAliyun = "aliyun"
|
||||
)
|
||||
|
||||
const (
|
||||
targetAliyunOss = "aliyun-oss"
|
||||
targetAliyunCdn = "aliyun-cdn"
|
||||
targetSSH = "ssh"
|
||||
targetWebhook = "webhook"
|
||||
targetTencentCdn = "tencent-cdn"
|
||||
targetQiniuCdn = "qiniu-cdn"
|
||||
)
|
||||
|
||||
type DeployerOption struct {
|
||||
@@ -63,6 +60,8 @@ func Get(record *models.Record, cert *applicant.Certificate) (Deployer, error) {
|
||||
return NewWebhook(option)
|
||||
case targetTencentCdn:
|
||||
return NewTencentCdn(option)
|
||||
case targetQiniuCdn:
|
||||
return NewQiNiu(option)
|
||||
}
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
208
internal/deployer/qiniu.go
Normal file
208
internal/deployer/qiniu.go
Normal file
@@ -0,0 +1,208 @@
|
||||
package deployer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"certimate/internal/domain"
|
||||
xhttp "certimate/internal/utils/http"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/qiniu/go-sdk/v7/auth"
|
||||
)
|
||||
|
||||
const qiniuGateway = "http://api.qiniu.com"
|
||||
|
||||
type qiuniu struct {
|
||||
option *DeployerOption
|
||||
info []string
|
||||
credentials *auth.Credentials
|
||||
}
|
||||
|
||||
func NewQiNiu(option *DeployerOption) (*qiuniu, error) {
|
||||
access := &domain.QiniuAccess{}
|
||||
json.Unmarshal([]byte(option.Access), access)
|
||||
|
||||
return &qiuniu{
|
||||
option: option,
|
||||
info: make([]string, 0),
|
||||
|
||||
credentials: auth.New(access.AccessKey, access.SecretKey),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (q *qiuniu) GetInfo() []string {
|
||||
return q.info
|
||||
}
|
||||
|
||||
func (q *qiuniu) Deploy(ctx context.Context) error {
|
||||
|
||||
// 上传证书
|
||||
certId, err := q.uploadCert()
|
||||
if err != nil {
|
||||
return fmt.Errorf("uploadCert failed: %w", err)
|
||||
}
|
||||
|
||||
// 获取域名信息
|
||||
domainInfo, err := q.getDomainInfo()
|
||||
if err != nil {
|
||||
return fmt.Errorf("getDomainInfo failed: %w", err)
|
||||
}
|
||||
|
||||
// 判断域名是否启用 https
|
||||
|
||||
if domainInfo.Https != nil && domainInfo.Https.CertID != "" {
|
||||
// 启用了 https
|
||||
// 修改域名证书
|
||||
err = q.modifyDomainCert(certId)
|
||||
if err != nil {
|
||||
return fmt.Errorf("modifyDomainCert failed: %w", err)
|
||||
}
|
||||
} else {
|
||||
// 没启用 https
|
||||
// 启用 https
|
||||
|
||||
err = q.enableHttps(certId)
|
||||
if err != nil {
|
||||
return fmt.Errorf("enableHttps failed: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *qiuniu) enableHttps(certId string) error {
|
||||
path := fmt.Sprintf("/domain/%s/sslize", q.option.Domain)
|
||||
|
||||
body := &modifyDomainCertReq{
|
||||
CertID: certId,
|
||||
ForceHttps: true,
|
||||
Http2Enable: true,
|
||||
}
|
||||
|
||||
bodyBytes, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("enable https failed: %w", err)
|
||||
}
|
||||
|
||||
_, err = q.req(qiniuGateway+path, http.MethodPut, bytes.NewReader(bodyBytes))
|
||||
if err != nil {
|
||||
return fmt.Errorf("enable https failed: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type domainInfo struct {
|
||||
Https *modifyDomainCertReq `json:"https"`
|
||||
}
|
||||
|
||||
func (q *qiuniu) getDomainInfo() (*domainInfo, error) {
|
||||
path := fmt.Sprintf("/domain/%s", q.option.Domain)
|
||||
|
||||
res, err := q.req(qiniuGateway+path, http.MethodGet, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("req failed: %w", err)
|
||||
}
|
||||
|
||||
resp := &domainInfo{}
|
||||
err = json.Unmarshal(res, resp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("json.Unmarshal failed: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
type uploadCertReq struct {
|
||||
Name string `json:"name"`
|
||||
CommonName string `json:"common_name"`
|
||||
Pri string `json:"pri"`
|
||||
Ca string `json:"ca"`
|
||||
}
|
||||
|
||||
type uploadCertResp struct {
|
||||
CertID string `json:"certID"`
|
||||
}
|
||||
|
||||
func (q *qiuniu) uploadCert() (string, error) {
|
||||
path := "/sslcert"
|
||||
|
||||
body := &uploadCertReq{
|
||||
Name: q.option.Domain,
|
||||
CommonName: q.option.Domain,
|
||||
Pri: q.option.Certificate.PrivateKey,
|
||||
Ca: q.option.Certificate.Certificate,
|
||||
}
|
||||
|
||||
bodyBytes, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("json.Marshal failed: %w", err)
|
||||
}
|
||||
|
||||
res, err := q.req(qiniuGateway+path, http.MethodPost, bytes.NewReader(bodyBytes))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("req failed: %w", err)
|
||||
}
|
||||
resp := &uploadCertResp{}
|
||||
err = json.Unmarshal(res, resp)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("json.Unmarshal failed: %w", err)
|
||||
}
|
||||
|
||||
return resp.CertID, nil
|
||||
}
|
||||
|
||||
type modifyDomainCertReq struct {
|
||||
CertID string `json:"certId"`
|
||||
ForceHttps bool `json:"forceHttps"`
|
||||
Http2Enable bool `json:"http2Enable"`
|
||||
}
|
||||
|
||||
func (q *qiuniu) modifyDomainCert(certId string) error {
|
||||
path := fmt.Sprintf("/domain/%s/httpsconf", q.option.Domain)
|
||||
|
||||
body := &modifyDomainCertReq{
|
||||
CertID: certId,
|
||||
ForceHttps: true,
|
||||
Http2Enable: true,
|
||||
}
|
||||
|
||||
bodyBytes, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("json.Marshal failed: %w", err)
|
||||
}
|
||||
|
||||
_, err = q.req(qiniuGateway+path, http.MethodPut, bytes.NewReader(bodyBytes))
|
||||
if err != nil {
|
||||
return fmt.Errorf("req failed: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *qiuniu) req(url, method string, body io.Reader) ([]byte, error) {
|
||||
req := xhttp.BuildReq(url, method, body, map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
})
|
||||
|
||||
if err := q.credentials.AddToken(auth.TokenQBox, req); err != nil {
|
||||
return nil, fmt.Errorf("credentials.AddToken failed: %w", err)
|
||||
}
|
||||
|
||||
respBody, err := xhttp.ToRequest(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ToRequest failed: %w", err)
|
||||
}
|
||||
|
||||
defer respBody.Close()
|
||||
|
||||
res, err := io.ReadAll(respBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("io.ReadAll failed: %w", err)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
86
internal/deployer/qiniu_test.go
Normal file
86
internal/deployer/qiniu_test.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package deployer
|
||||
|
||||
import (
|
||||
"certimate/internal/applicant"
|
||||
"testing"
|
||||
|
||||
"github.com/qiniu/go-sdk/v7/auth"
|
||||
)
|
||||
|
||||
func Test_qiuniu_uploadCert(t *testing.T) {
|
||||
type fields struct {
|
||||
option *DeployerOption
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "test",
|
||||
fields: fields{
|
||||
option: &DeployerOption{
|
||||
DomainId: "1",
|
||||
Domain: "example.com",
|
||||
Product: "test",
|
||||
Access: `{"bucket":"test","accessKey":"","secretKey":""}`,
|
||||
Certificate: applicant.Certificate{
|
||||
Certificate: "",
|
||||
PrivateKey: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
q, _ := NewQiNiu(tt.fields.option)
|
||||
got, err := q.uploadCert()
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("qiuniu.uploadCert() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("qiuniu.uploadCert() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_qiuniu_modifyDomainCert(t *testing.T) {
|
||||
type fields struct {
|
||||
option *DeployerOption
|
||||
info []string
|
||||
credentials *auth.Credentials
|
||||
}
|
||||
type args struct {
|
||||
certId string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "test",
|
||||
fields: fields{
|
||||
option: &DeployerOption{
|
||||
DomainId: "1",
|
||||
Domain: "jt1.ikit.fun",
|
||||
Product: "test",
|
||||
Access: `{"bucket":"test","accessKey":"","secretKey":""}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
q, _ := NewQiNiu(tt.fields.option)
|
||||
if err := q.modifyDomainCert(tt.args.certId); (err != nil) != tt.wantErr {
|
||||
t.Errorf("qiuniu.modifyDomainCert() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@@ -13,3 +13,8 @@ type TencentAccess struct {
|
||||
type CloudflareAccess struct {
|
||||
DnsApiToken string `json:"dnsApiToken"`
|
||||
}
|
||||
|
||||
type QiniuAccess struct {
|
||||
AccessKey string `json:"accessKey"`
|
||||
SecretKey string `json:"secretKey"`
|
||||
}
|
||||
|
@@ -32,6 +32,24 @@ func Req(url string, method string, body io.Reader, head map[string]string, opts
|
||||
}
|
||||
|
||||
func Req2GetReader(url string, method string, body io.Reader, head map[string]string, opts ...Option) (io.ReadCloser, error) {
|
||||
req := BuildReq(url, method, body, head)
|
||||
|
||||
return ToRequest(req, opts...)
|
||||
|
||||
}
|
||||
|
||||
func BuildReq(url string, method string, body io.Reader, head map[string]string) *http.Request {
|
||||
|
||||
// Create an http.Request instance
|
||||
req, _ := http.NewRequest(method, url, body)
|
||||
for k, v := range head {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
func ToRequest(req *http.Request, opts ...Option) (io.ReadCloser, error) {
|
||||
options := &Options{
|
||||
Timeout: 30000 * time.Millisecond,
|
||||
}
|
||||
@@ -39,13 +57,8 @@ func Req2GetReader(url string, method string, body io.Reader, head map[string]st
|
||||
for _, opt := range opts {
|
||||
opt(options)
|
||||
}
|
||||
client := httpclient.NewClient(httpclient.WithHTTPTimeout(options.Timeout))
|
||||
|
||||
// Create an http.Request instance
|
||||
req, _ := http.NewRequest(method, url, body)
|
||||
for k, v := range head {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
client := httpclient.NewClient(httpclient.WithHTTPTimeout(options.Timeout))
|
||||
// Call the `Do` method, which has a similar interface to the `http.Do` method
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
@@ -53,6 +66,9 @@ func Req2GetReader(url string, method string, body io.Reader, head map[string]st
|
||||
}
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
defer res.Body.Close()
|
||||
body, _ := io.ReadAll(res.Body)
|
||||
fmt.Println(string(body))
|
||||
return nil, fmt.Errorf("status code is not 200: %d", res.StatusCode)
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user