diff --git a/Dockerfile b/Dockerfile index 6443b0b..cfe44c1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,6 +13,7 @@ COPY ./conf /opt/app/conf COPY ./util/merge-signature.py /opt/app/util/merge-signature.py # overwrite config yaml COPY ./deploy/app.conf /opt/app/conf +COPY ./deploy/app.conf.yaml /opt/app/conf COPY --from=BUILDER /go/src/github.com/opensourceways/app-cla-server/cla-server /opt/app WORKDIR /opt/app/ diff --git a/conf/app.conf b/conf/app.conf index c902b66..fe9c1ca 100644 --- a/conf/app.conf +++ b/conf/app.conf @@ -5,30 +5,4 @@ autorender = false copyrequestbody = true EnableDocs = true -python_bin = /usr/bin/python3 - -cla_fields_number = 10 -cla_platform_url = https://clasign.osinfra.cn - -verification_code_expiry = 300 -api_token_expiry = 3600 -api_token_key = fsfsfsafsfsasaf242342424sdfs;.]{77&&& -symmetric_encryption_key = key-can-be--16-24-32-bytes-long! - -pdf_org_signature_dir = ./conf/org_signature_pdf -pdf_out_dir = ./conf/pdf - -code_platforms = ./conf/code_platforms.yaml -email_platforms = ./conf/email.yaml - -employee_managers_number = 5 - -[mongodb] -mongodb_conn = mongodb://root:123@localhost:32768 -mongodb_db = cla -link_collection = links -corp_pdf_collection = corp_pdfs -org_email_collection = org_emails -verification_code_collection = verification_codes -corp_signing_collection = corp_signings -individual_signing_collection = individual_signings +app_conf = ./conf/app.conf.yaml diff --git a/conf/app.conf.yaml b/conf/app.conf.yaml new file mode 100644 index 0000000..2da6885 --- /dev/null +++ b/conf/app.conf.yaml @@ -0,0 +1,32 @@ +python_bin: /usr/bin/python3 + +cla_fields_number: 10 +cla_platform_url: https://clasign.osinfra.cn + +verification_code_expiry: 300 +api_token_expiry: 3600 +api_token_key: fsfsfsafsfsasaf242342424sdfs;.]{77&&& +symmetric_encryption_key: key-can-be--16-24-32-bytes-long! + +pdf_org_signature_dir: ./conf/org_signature_pdf +pdf_out_dir: ./conf/pdf + +code_platforms: ./conf/code_platforms.yaml +email_platforms: ./conf/email.yaml + +employee_managers_number: 5 + +mongodb: + mongodb_conn: mongodb://root:123@localhost:32768 + mongodb_db: cla + link_collection: links + corp_pdf_collection: corp_pdfs + org_email_collection: org_emails + verification_code_collection: verification_codes + corp_signing_collection: corp_signings + individual_signing_collection: individual_signings + +obs: + name: huaweicloud-obs + bucket: cla + credential_file: ./conf/huaweicloud_obs_credential.yaml diff --git a/conf/huaweicloud_obs_credential.yaml b/conf/huaweicloud_obs_credential.yaml new file mode 100644 index 0000000..7c5d4b9 --- /dev/null +++ b/conf/huaweicloud_obs_credential.yaml @@ -0,0 +1,4 @@ +access_key: {{ access key }} +secret_key: {{ secret key }} +endpoint: https://obs.{{ region }}.myhuaweicloud.com +object_encryption_key: "{{ AES256 encryption key }}" diff --git a/config/config.go b/config/config.go index 7753ad0..77820d7 100644 --- a/config/config.go +++ b/config/config.go @@ -3,13 +3,21 @@ package config import ( "fmt" - "github.com/astaxie/beego" - "github.com/huaweicloud/golangsdk" - "github.com/opensourceways/app-cla-server/util" ) -var AppConfig *appConfig +var AppConfig = &appConfig{} + +func InitAppConfig(path string) error { + cfg := AppConfig + if err := util.LoadFromYaml(path, cfg); err != nil { + return err + } + + cfg.setDefault() + + return cfg.validate() +} type appConfig struct { PythonBin string `json:"python_bin" required:"true"` @@ -29,6 +37,7 @@ type appConfig struct { EmployeeManagersNumber int `json:"employee_managers_number" required:"true"` CLAPlatformURL string `json:"cla_platform_url" required:"true"` Mongodb MongodbConfig `json:"mongodb" required:"true"` + OBS OBS `json:"obs" required:"true"` } type MongodbConfig struct { @@ -42,112 +51,72 @@ type MongodbConfig struct { IndividualSigningCollection string `json:"individual_signing_collection" required:"true"` } -func InitAppConfig() error { - claFieldsNumber, err := beego.AppConfig.Int("cla_fields_number") - if err != nil { - return err - } - - maxSizeOfCorpCLAPDF := beego.AppConfig.DefaultInt("max_size_of_corp_cla_pdf", (2 << 20)) - maxSizeOfOrgSignaturePDF := beego.AppConfig.DefaultInt("max_size_of_org_signature_pdf", (1 << 20)) - minLengthOfPassword := beego.AppConfig.DefaultInt("min_length_of_password", 6) - maxLengthOfPassword := beego.AppConfig.DefaultInt("max_length_of_password", 16) +type OBS struct { + Name string `json:"name" required:"true"` + Bucket string `json:"bucket" required:"true"` + CredentialFile string `json:"credential_file" required:"true"` +} - tokenExpiry, err := beego.AppConfig.Int64("api_token_expiry") - if err != nil { - return err +func (cfg *appConfig) setDefault() { + if cfg.MaxSizeOfCorpCLAPDF <= 0 { + cfg.MaxSizeOfCorpCLAPDF = (5 << 20) } - - codeExpiry, err := beego.AppConfig.Int64("verification_code_expiry") - if err != nil { - return err + if cfg.MaxSizeOfOrgSignaturePDF <= 0 { + cfg.MaxSizeOfOrgSignaturePDF = (1 << 20) } - employeeMangers, err := beego.AppConfig.Int("employee_managers_number") - if err != nil { - return err + if cfg.MinLengthOfPassword <= 0 { + cfg.MinLengthOfPassword = 6 } - AppConfig = &appConfig{ - PythonBin: beego.AppConfig.String("python_bin"), - CLAFieldsNumber: claFieldsNumber, - MaxSizeOfCorpCLAPDF: maxSizeOfCorpCLAPDF, - MaxSizeOfOrgSignaturePDF: maxSizeOfOrgSignaturePDF, - MinLengthOfPassword: minLengthOfPassword, - MaxLengthOfPassword: maxLengthOfPassword, - VerificationCodeExpiry: codeExpiry, - APITokenExpiry: tokenExpiry, - APITokenKey: beego.AppConfig.String("api_token_key"), - SymmetricEncryptionKey: beego.AppConfig.String("symmetric_encryption_key"), - PDFOrgSignatureDir: beego.AppConfig.String("pdf_org_signature_dir"), - PDFOutDir: beego.AppConfig.String("pdf_out_dir"), - CodePlatformConfigFile: beego.AppConfig.String("code_platforms"), - EmailPlatformConfigFile: beego.AppConfig.String("email_platforms"), - EmployeeManagersNumber: employeeMangers, - CLAPlatformURL: beego.AppConfig.String("cla_platform_url"), - Mongodb: MongodbConfig{ - MongodbConn: beego.AppConfig.String("mongodb::mongodb_conn"), - DBName: beego.AppConfig.String("mongodb::mongodb_db"), - LinkCollection: beego.AppConfig.String("mongodb::link_collection"), - OrgEmailCollection: beego.AppConfig.String("mongodb::org_email_collection"), - CorpPDFCollection: beego.AppConfig.String("mongodb::corp_pdf_collection"), - VCCollection: beego.AppConfig.String("mongodb::verification_code_collection"), - CorpSigningCollection: beego.AppConfig.String("mongodb::corp_signing_collection"), - IndividualSigningCollection: beego.AppConfig.String("mongodb::individual_signing_collection"), - }, - } - return AppConfig.validate() -} - -func (this *appConfig) validate() error { - _, err := golangsdk.BuildRequestBody(this, "") - if err != nil { - return fmt.Errorf("config file error: %s", err.Error()) + if cfg.MaxLengthOfPassword <= 0 { + cfg.MaxLengthOfPassword = 16 } +} - if util.IsFileNotExist(this.PythonBin) { - return fmt.Errorf("The file:%s is not exist", this.PythonBin) +func (cfg *appConfig) validate() error { + if util.IsFileNotExist(cfg.PythonBin) { + return fmt.Errorf("The file:%s is not exist", cfg.PythonBin) } - if this.CLAFieldsNumber <= 0 { - return fmt.Errorf("The cla_fields_number:%d should be bigger than 0", this.CLAFieldsNumber) + if cfg.CLAFieldsNumber <= 0 { + return fmt.Errorf("The cla_fields_number:%d should be bigger than 0", cfg.CLAFieldsNumber) } - if this.VerificationCodeExpiry <= 0 { - return fmt.Errorf("The verification_code_expiry:%d should be bigger than 0", this.VerificationCodeExpiry) + if cfg.VerificationCodeExpiry <= 0 { + return fmt.Errorf("The verification_code_expiry:%d should be bigger than 0", cfg.VerificationCodeExpiry) } - if this.APITokenExpiry <= 0 { - return fmt.Errorf("The apit_oken_expiry:%d should be bigger than 0", this.APITokenExpiry) + if cfg.APITokenExpiry <= 0 { + return fmt.Errorf("The apit_oken_expiry:%d should be bigger than 0", cfg.APITokenExpiry) } - if this.EmployeeManagersNumber <= 0 { - return fmt.Errorf("The employee_managers_number:%d should be bigger than 0", this.EmployeeManagersNumber) + if cfg.EmployeeManagersNumber <= 0 { + return fmt.Errorf("The employee_managers_number:%d should be bigger than 0", cfg.EmployeeManagersNumber) } - if len(this.APITokenKey) < 20 { + if len(cfg.APITokenKey) < 20 { return fmt.Errorf("The length of api_token_key should be bigger than 20") } - if err := util.IsSymmetricEncryptionKeyValid(this.SymmetricEncryptionKey); err != nil { + if err := util.IsSymmetricEncryptionKeyValid(cfg.SymmetricEncryptionKey); err != nil { return fmt.Errorf("The symmetric encryption key is not valid, %s", err.Error()) } - if util.IsNotDir(this.PDFOrgSignatureDir) { - return fmt.Errorf("The directory:%s is not exist", this.PDFOrgSignatureDir) + if util.IsNotDir(cfg.PDFOrgSignatureDir) { + return fmt.Errorf("The directory:%s is not exist", cfg.PDFOrgSignatureDir) } - if util.IsNotDir(this.PDFOutDir) { - return fmt.Errorf("The directory:%s is not exist", this.PDFOutDir) - + if util.IsNotDir(cfg.PDFOutDir) { + return fmt.Errorf("The directory:%s is not exist", cfg.PDFOutDir) } - if util.IsFileNotExist(this.CodePlatformConfigFile) { - return fmt.Errorf("The file:%s is not exist", this.CodePlatformConfigFile) + if util.IsFileNotExist(cfg.CodePlatformConfigFile) { + return fmt.Errorf("The file:%s is not exist", cfg.CodePlatformConfigFile) } - if util.IsFileNotExist(this.EmailPlatformConfigFile) { - return fmt.Errorf("The file:%s is not exist", this.EmailPlatformConfigFile) + if util.IsFileNotExist(cfg.EmailPlatformConfigFile) { + return fmt.Errorf("The file:%s is not exist", cfg.EmailPlatformConfigFile) } return nil diff --git a/controllers/corporation-pdf.go b/controllers/corporation-pdf.go index 503633c..0a9b831 100644 --- a/controllers/corporation-pdf.go +++ b/controllers/corporation-pdf.go @@ -35,12 +35,14 @@ func (this *CorporationPDFController) downloadCorpPDF(linkID, corpEmail string) if err != nil { return newFailedApiResult(500, errSystemError, err) } + path := f.Name() + f.Close() + defer func() { - f.Close() - os.Remove(f.Name()) + os.Remove(path) }() - pdf, merr := models.DownloadCorporationSigningPDF(linkID, corpEmail) + merr := models.DownloadCorporationSigningPDF(linkID, corpEmail, path) if merr != nil { if merr.IsErrorOf(models.ErrNoLinkOrUnuploaed) { return newFailedApiResult(400, errUnuploaded, merr) @@ -48,12 +50,7 @@ func (this *CorporationPDFController) downloadCorpPDF(linkID, corpEmail string) return parseModelError(merr) } - if _, err = f.Write(*pdf); err != nil { - return newFailedApiResult(500, errSystemError, err) - } - - f.Close() - this.downloadFile(f.Name()) + this.downloadFile(path) return nil } @@ -102,7 +99,7 @@ func (this *CorporationPDFController) Upload() { return } - if err := models.UploadCorporationSigningPDF(linkID, corpEmail, &data); err != nil { + if err := models.UploadCorporationSigningPDF(linkID, corpEmail, data); err != nil { this.sendModelErrorAsResp(err, action) return } diff --git a/dbmodels/db.go b/dbmodels/db.go index 40b1dc5..865f2f9 100644 --- a/dbmodels/db.go +++ b/dbmodels/db.go @@ -11,6 +11,11 @@ func GetDB() IDB { } type IDB interface { + IModel + IFile +} + +type IModel interface { ILink ICorporationSigning ICorporationManager @@ -29,11 +34,13 @@ type ICorporationSigning interface { ListDeletedCorpSignings(linkID string) ([]CorporationSigningBasicInfo, IDBError) GetCorpSigningDetail(linkID, email string) ([]Field, *CorpSigningCreateOpt, IDBError) GetCorpSigningBasicInfo(linkID, email string) (*CorporationSigningBasicInfo, IDBError) +} - UploadCorporationSigningPDF(linkID string, adminEmail string, pdf *[]byte) IDBError - DownloadCorporationSigningPDF(linkID string, email string) (*[]byte, IDBError) - IsCorpSigningPDFUploaded(linkID string, email string) (bool, IDBError) - ListCorpsWithPDFUploaded(linkID string) ([]string, IDBError) +type IFile interface { + UploadCorporationSigningPDF(linkID, adminEmail string, pdf []byte) IDBError + DownloadCorporationSigningPDF(linkID, email, path string) IDBError + IsCorporationSigningPDFUploaded(linkID, email string) (bool, IDBError) + ListCorporationsWithPDFUploaded(linkID string) ([]string, IDBError) } type ICorporationManager interface { diff --git a/dbmodels/error.go b/dbmodels/error.go index 762ef24..c73f8e4 100644 --- a/dbmodels/error.go +++ b/dbmodels/error.go @@ -14,3 +14,27 @@ const ( ErrRecordExists DBErrCode = "db_record_exists" ErrMarshalDataFaield DBErrCode = "failed_to_marshal_data" ) + +type dbError struct { + code DBErrCode + err error +} + +func (e dbError) Error() string { + if e.err == nil { + return "" + } + return e.err.Error() +} + +func (e dbError) IsErrorOf(code DBErrCode) bool { + return e.code == code +} + +func (e dbError) ErrCode() DBErrCode { + return e.code +} + +func NewDBError(code DBErrCode, err error) IDBError { + return dbError{code: code, err: err} +} diff --git a/deploy/app.conf b/deploy/app.conf index 82c79fa..f1f85e0 100644 --- a/deploy/app.conf +++ b/deploy/app.conf @@ -5,35 +5,4 @@ autorender = false copyrequestbody = true EnableDocs = false -python_bin = /usr/bin/python3 - -cla_fields_number = 10 - -verification_code_expiry = 300 -api_token_expiry = 1800 -api_token_key = "${API_TOKEN_KEY}" -symmetric_encryption_key = "${SYMMETRIC_ENCRYPTION_KEY}" - -pdf_org_signature_dir = ./conf/pdfs/org_signature_pdf -pdf_out_dir = ./conf/pdfs/output - -code_platforms = ./conf/platforms/code_platforms.yaml -email_platforms = ./conf/platforms/email.yaml - -employee_managers_number = 5 - -max_size_of_corp_cla_pdf = 5242880 -max_size_of_org_signature_pdfa = 204800 - -cla_platform_url = "${CLA_PLATFORM_URL}" - -[mongodb] -mongodb_conn = "${MONGODB_CONNECTION}" -mongodb_db = "${MONGODB_NAME}" - -link_collection = links -corp_pdf_collection = corp_pdfs -org_email_collection = org_emails -verification_code_collection = verification_codes -corp_signing_collection = corp_signings -individual_signing_collection = individual_signings +app_conf = ./conf/app.conf.yaml diff --git a/deploy/app.conf.yaml b/deploy/app.conf.yaml new file mode 100644 index 0000000..d5a6291 --- /dev/null +++ b/deploy/app.conf.yaml @@ -0,0 +1,36 @@ +python_bin: /usr/bin/python3 + +cla_fields_number: 10 + +verification_code_expiry: 300 +api_token_expiry: 1800 +api_token_key: "${API_TOKEN_KEY}" +symmetric_encryption_key: "${SYMMETRIC_ENCRYPTION_KEY}" + +pdf_org_signature_dir: ./conf/pdfs/org_signature_pdf +pdf_out_dir: ./conf/pdfs/output + +code_platforms: ./conf/platforms/code_platforms.yaml +email_platforms: ./conf/platforms/email.yaml + +employee_managers_number: 5 + +max_size_of_corp_cla_pdf: 5242880 +max_size_of_org_signature_pdfa: 204800 + +cla_platform_url: "${CLA_PLATFORM_URL}" + +mongodb: + mongodb_conn: "${MONGODB_CONNECTION}" + mongodb_db: "${MONGODB_NAME}" + link_collection: links + corp_pdf_collection: corp_pdfs + org_email_collection: org_emails + verification_code_collection: verification_codes + corp_signing_collection: corp_signings + individual_signing_collection: individual_signings + +obs: + name: huaweicloud-obs + bucket: cla + credential_file: ./conf/platforms/huaweicloud_obs_credential.yaml diff --git a/go.mod b/go.mod index 639d666..13ae562 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/google/go-github/v33 v33.0.0 github.com/huaweicloud/golangsdk v0.0.0-20201228013212-d10065a3dc7f + github.com/huaweicloud/huaweicloud-sdk-go-obs v3.21.1+incompatible github.com/opensourceways/gofpdf v1.16.4 go.mongodb.org/mongo-driver v1.4.4 golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a diff --git a/go.sum b/go.sum index fdfa6f5..66a9332 100644 --- a/go.sum +++ b/go.sum @@ -195,6 +195,8 @@ github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uG github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huaweicloud/golangsdk v0.0.0-20201228013212-d10065a3dc7f h1:5gf/WhTTYklU59Ah+hcnCkQAyQzlylglysX6E8I82f8= github.com/huaweicloud/golangsdk v0.0.0-20201228013212-d10065a3dc7f/go.mod h1:fcOI5u+0f62JtJd7zkCch/Z57BNC6bhqb32TKuiF4r0= +github.com/huaweicloud/huaweicloud-sdk-go-obs v3.21.1+incompatible h1:EFjtiulITiEktaZrr0OPlymTmrlpvSAa/xvv08kTQEU= +github.com/huaweicloud/huaweicloud-sdk-go-obs v3.21.1+incompatible/go.mod h1:l7VUhRbTKCzdOacdT4oWCwATKyvZqUOlOqr0Ous3k4s= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= diff --git a/main.go b/main.go index 6e75e84..0911021 100644 --- a/main.go +++ b/main.go @@ -11,6 +11,8 @@ import ( "github.com/opensourceways/app-cla-server/dbmodels" "github.com/opensourceways/app-cla-server/email" "github.com/opensourceways/app-cla-server/mongodb" + "github.com/opensourceways/app-cla-server/obs" + _ "github.com/opensourceways/app-cla-server/obs/huaweicloud" "github.com/opensourceways/app-cla-server/pdf" _ "github.com/opensourceways/app-cla-server/routers" "github.com/opensourceways/app-cla-server/util" @@ -23,7 +25,7 @@ func main() { beego.BConfig.WebConfig.StaticDir["/swagger"] = "swagger" } - if err := config.InitAppConfig(); err != nil { + if err := config.InitAppConfig(beego.AppConfig.String("app_conf")); err != nil { beego.Error(err) os.Exit(1) } @@ -38,12 +40,25 @@ func main() { } } - c, err := mongodb.Initialize(&AppConfig.Mongodb) + mongoClient, err := mongodb.Initialize(&AppConfig.Mongodb) if err != nil { beego.Error(err) os.Exit(1) } - dbmodels.RegisterDB(c) + + obsClient, err := obs.Initialize(AppConfig.OBS) + if err != nil { + beego.Error(err) + os.Exit(1) + } + + dbmodels.RegisterDB(struct { + dbmodels.IModel + dbmodels.IFile + }{ + IModel: mongoClient, + IFile: obs.NewFileStorage(obsClient), + }) if err = email.Initialize(AppConfig.EmailPlatformConfigFile); err != nil { beego.Error(err) diff --git a/models/corporation-signing.go b/models/corporation-signing.go index b99ac43..9764ec3 100644 --- a/models/corporation-signing.go +++ b/models/corporation-signing.go @@ -42,30 +42,30 @@ func (this *CorporationSigningCreateOption) Create(orgCLAID string) IModelError return parseDBError(err) } -func UploadCorporationSigningPDF(linkID string, email string, pdf *[]byte) IModelError { +func UploadCorporationSigningPDF(linkID, email string, pdf []byte) IModelError { err := dbmodels.GetDB().UploadCorporationSigningPDF(linkID, email, pdf) return parseDBError(err) } -func DownloadCorporationSigningPDF(linkID string, email string) (*[]byte, IModelError) { - v, err := dbmodels.GetDB().DownloadCorporationSigningPDF(linkID, email) +func DownloadCorporationSigningPDF(linkID, email, path string) IModelError { + err := dbmodels.GetDB().DownloadCorporationSigningPDF(linkID, email, path) if err == nil { - return v, nil + return nil } if err.IsErrorOf(dbmodels.ErrNoDBRecord) { - return v, newModelError(ErrNoLinkOrUnuploaed, err) + return newModelError(ErrNoLinkOrUnuploaed, err) } - return v, parseDBError(err) + return parseDBError(err) } func IsCorpSigningPDFUploaded(linkID string, email string) (bool, IModelError) { - v, err := dbmodels.GetDB().IsCorpSigningPDFUploaded(linkID, email) + v, err := dbmodels.GetDB().IsCorporationSigningPDFUploaded(linkID, email) return v, parseDBError(err) } func ListCorpsWithPDFUploaded(linkID string) ([]string, IModelError) { - v, err := dbmodels.GetDB().ListCorpsWithPDFUploaded(linkID) + v, err := dbmodels.GetDB().ListCorporationsWithPDFUploaded(linkID) return v, parseDBError(err) } diff --git a/mongodb/mongodb.go b/mongodb/mongodb.go index e76bd3e..f6579a2 100644 --- a/mongodb/mongodb.go +++ b/mongodb/mongodb.go @@ -13,7 +13,7 @@ import ( "github.com/opensourceways/app-cla-server/dbmodels" ) -var _ dbmodels.IDB = (*client)(nil) +var _ dbmodels.IModel = (*client)(nil) type client struct { c *mongo.Client diff --git a/obs/db.go b/obs/db.go new file mode 100644 index 0000000..89decab --- /dev/null +++ b/obs/db.go @@ -0,0 +1,65 @@ +package obs + +import ( + "fmt" + "strings" + + "github.com/opensourceways/app-cla-server/dbmodels" + "github.com/opensourceways/app-cla-server/util" +) + +func NewFileStorage(c OBS) dbmodels.IFile { + return fileStorage{c: c} +} + +type fileStorage struct { + c OBS +} + +func (fs fileStorage) UploadCorporationSigningPDF(linkID, adminEmail string, pdf []byte) dbmodels.IDBError { + err := fs.c.WriteObject(buildCorpSigningPDFPath(linkID, adminEmail), pdf) + return toDBError(err) +} + +func (fs fileStorage) DownloadCorporationSigningPDF(linkID, email, path string) dbmodels.IDBError { + err := fs.c.ReadObject(buildCorpSigningPDFPath(linkID, email), path) + if err == nil { + return nil + } + + if err.IsObjectNotFound() { + return dbmodels.NewDBError(dbmodels.ErrNoDBRecord, err) + } + return toDBError(err) +} + +func (fs fileStorage) IsCorporationSigningPDFUploaded(linkID, email string) (bool, dbmodels.IDBError) { + b, err := fs.c.HasObject(buildCorpSigningPDFPath(linkID, email)) + return b, toDBError(err) +} + +func (fs fileStorage) ListCorporationsWithPDFUploaded(linkID string) ([]string, dbmodels.IDBError) { + prefix := buildCorpSigningPDFPath(linkID, "") + + r, err := fs.c.ListObject(prefix) + if err != nil { + return nil, toDBError(err) + } + + result := make([]string, 0, len(r)) + for _, item := range r { + result = append(result, strings.TrimPrefix(item, prefix)) + } + return result, nil +} + +func buildCorpSigningPDFPath(linkID string, email string) string { + return fmt.Sprintf("%s/%s", linkID, util.EmailSuffix(email)) +} + +func toDBError(err error) dbmodels.IDBError { + if err == nil { + return nil + } + return dbmodels.NewDBError(dbmodels.ErrSystemError, err) +} diff --git a/obs/huaweicloud/client.go b/obs/huaweicloud/client.go new file mode 100644 index 0000000..0d734a3 --- /dev/null +++ b/obs/huaweicloud/client.go @@ -0,0 +1,146 @@ +package huaweicloud + +import ( + "bytes" + + sdk "github.com/huaweicloud/huaweicloud-sdk-go-obs/obs" + + "github.com/opensourceways/app-cla-server/obs" +) + +const plugin = "huaweicloud-obs" + +type client struct { + c *sdk.ObsClient + + bucket string + ssecHeader sdk.ISseHeader +} + +func init() { + obs.Register(plugin, &client{}) +} + +func (cli *client) Initialize(path, bucket string) error { + cfg, err := loadConfig(path) + if err != nil { + return err + } + + c, err := sdk.New(cfg.AccessKey, cfg.SecretKey, cfg.Endpoint) + if err != nil { + return err + } + + if _, err = c.GetBucketLocation(bucket); err != nil { + return err + } + + cli.c = c + cli.bucket = bucket + cli.ssecHeader = newSSECHeader(cfg.ObjectEncryptionKey) + + return nil +} + +func (cli *client) WriteObject(path string, data []byte) error { + input := sdk.PutObjectInput{Body: bytes.NewReader(data)} + input.Bucket = cli.bucket + input.Key = path + input.SseHeader = cli.ssecHeader + input.ContentMD5 = sdk.Base64Md5(data) + + _, err := cli.c.PutObject(&input) + return err +} + +func (cli *client) ReadObject(path, localPath string) obs.OBSError { + input := sdk.DownloadFileInput{DownloadFile: localPath} + input.Bucket = cli.bucket + input.Key = path + input.SseHeader = cli.ssecHeader + + _, err := cli.c.DownloadFile(&input) + if err == nil { + return nil + } + + return obsError{err: err} +} + +func (cli *client) HasObject(path string) (bool, error) { + input := sdk.GetObjectMetadataInput{ + Bucket: cli.bucket, + Key: path, + SseHeader: cli.ssecHeader, + } + _, err := cli.c.GetObjectMetadata(&input) + if err == nil { + return true, nil + } + + e := obsError{err: err} + if e.IsObjectNotFound() { + return false, nil + } + + return false, err +} + +func (cli *client) ListObject(pathPrefix string) ([]string, error) { + input := sdk.ListObjectsInput{ + Bucket: cli.bucket, + } + if pathPrefix != "" { + input.Prefix = pathPrefix + } + + r := make([]string, 0, 100) + for { + output, err := cli.c.ListObjects(&input) + if err != nil { + return nil, err + } + + for i := range output.Contents { + r = append(r, output.Contents[i].Key) + } + + if output.IsTruncated { + input.Marker = output.NextMarker + } else { + break + } + } + + return r, nil +} + +func newSSECHeader(key string) sdk.ISseHeader { + if key == "" { + return nil + } + + h := sdk.SseCHeader{ + Key: sdk.Base64Encode([]byte(key)), + } + h.KeyMD5 = h.GetKeyMD5() + + return h +} + +type obsError struct { + err error +} + +func (e obsError) IsObjectNotFound() bool { + er, ok := e.err.(sdk.ObsError) + return ok && er.StatusCode == 404 +} + +func (e obsError) Error() string { + if e.err != nil { + return e.err.Error() + } + return "" +} diff --git a/obs/huaweicloud/config.go b/obs/huaweicloud/config.go new file mode 100644 index 0000000..9c6530a --- /dev/null +++ b/obs/huaweicloud/config.go @@ -0,0 +1,21 @@ +package huaweicloud + +import ( + "github.com/opensourceways/app-cla-server/util" +) + +type config struct { + AccessKey string `json:"access_key" required:"true"` + SecretKey string `json:"secret_key" required:"true"` + Endpoint string `json:"endpoint" required:"true"` + ObjectEncryptionKey string `json:"object_encryption_key"` +} + +func loadConfig(path string) (*config, error) { + v := &config{} + if err := util.LoadFromYaml(path, v); err != nil { + return nil, err + } + + return v, nil +} diff --git a/obs/obs.go b/obs/obs.go new file mode 100644 index 0000000..13a5e08 --- /dev/null +++ b/obs/obs.go @@ -0,0 +1,35 @@ +package obs + +import ( + "fmt" + + appConf "github.com/opensourceways/app-cla-server/config" +) + +type OBS interface { + Initialize(string, string) error + WriteObject(path string, data []byte) error + ReadObject(path, localPath string) OBSError + HasObject(string) (bool, error) + ListObject(pathPrefix string) ([]string, error) +} + +var instances = map[string]OBS{} + +func Register(plugin string, i OBS) { + instances[plugin] = i +} + +func Initialize(info appConf.OBS) (OBS, error) { + i, ok := instances[info.Name] + if !ok { + return nil, fmt.Errorf("no such obs instance of %s", info.Name) + } + + return i, i.Initialize(info.CredentialFile, info.Bucket) +} + +type OBSError interface { + Error() string + IsObjectNotFound() bool +} diff --git a/util/util.go b/util/util.go index 0abf6b2..2faa5b1 100644 --- a/util/util.go +++ b/util/util.go @@ -50,7 +50,9 @@ func LoadFromYaml(path string, cfg interface{}) error { return err } - if err := yaml.Unmarshal(b, cfg); err != nil { + content := []byte(os.ExpandEnv(string(b))) + + if err := yaml.Unmarshal(content, cfg); err != nil { return err }