322 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			322 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			Go
		
	
	
	
/*
 | 
						|
Copyright 2021 The Dapr Authors
 | 
						|
Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
you may not use this file except in compliance with the License.
 | 
						|
You may obtain a copy of the License at
 | 
						|
    http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
Unless required by applicable law or agreed to in writing, software
 | 
						|
distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
See the License for the specific language governing permissions and
 | 
						|
limitations under the License.
 | 
						|
*/
 | 
						|
 | 
						|
package obs
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"context"
 | 
						|
	"encoding/json"
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"strconv"
 | 
						|
 | 
						|
	"github.com/google/uuid"
 | 
						|
	"github.com/huaweicloud/huaweicloud-sdk-go-obs/obs"
 | 
						|
	"github.com/mitchellh/mapstructure"
 | 
						|
 | 
						|
	"github.com/dapr/components-contrib/bindings"
 | 
						|
	"github.com/dapr/kit/logger"
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	metadataKey = "key"
 | 
						|
	maxResults  = 1000
 | 
						|
)
 | 
						|
 | 
						|
// add operations that are not listed under the standard bindings operations.
 | 
						|
const (
 | 
						|
	UploadOperation bindings.OperationKind = "upload"
 | 
						|
)
 | 
						|
 | 
						|
type HuaweiOBS struct {
 | 
						|
	metadata *obsMetadata
 | 
						|
	service  HuaweiOBSAPI
 | 
						|
	logger   logger.Logger
 | 
						|
}
 | 
						|
 | 
						|
type obsMetadata struct {
 | 
						|
	Region    string `json:"region"`    // (optional) the specific Huawei region of the bucket
 | 
						|
	Endpoint  string `json:"endpoint"`  // the specific Huawei OBS endpoint
 | 
						|
	AccessKey string `json:"accessKey"` // the Huawei Access Key (AK) to access the obs
 | 
						|
	SecretKey string `json:"secretKey"` // the Huawei Secret Key (SK) to access the obs
 | 
						|
	Bucket    string `json:"bucket"`    // the name of the Huawei OBS bucket to write to
 | 
						|
}
 | 
						|
 | 
						|
type createResponse struct {
 | 
						|
	StatusCode int    `json:"statusCode"`
 | 
						|
	VersionID  string `json:"versionId"`
 | 
						|
}
 | 
						|
 | 
						|
type uploadPayload struct {
 | 
						|
	SourceFile string `json:"sourceFile"`
 | 
						|
}
 | 
						|
 | 
						|
type listPayload struct {
 | 
						|
	Marker     string `json:"marker"`
 | 
						|
	Prefix     string `json:"prefix"`
 | 
						|
	MaxResults int32  `json:"maxResults"`
 | 
						|
	Delimiter  string `json:"delimiter"`
 | 
						|
}
 | 
						|
 | 
						|
// NewHuaweiOBS returns a new Huawei OBS instance.
 | 
						|
func NewHuaweiOBS(logger logger.Logger) bindings.OutputBinding {
 | 
						|
	return &HuaweiOBS{logger: logger}
 | 
						|
}
 | 
						|
 | 
						|
// Init does metadata parsing and connection creation.
 | 
						|
func (o *HuaweiOBS) Init(metadata bindings.Metadata) error {
 | 
						|
	o.logger.Debugf("initializing Huawei OBS binding and parsing metadata")
 | 
						|
 | 
						|
	m, err := o.parseMetadata(metadata)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	client, err := obs.New(m.AccessKey, m.SecretKey, m.Endpoint)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	o.metadata = m
 | 
						|
	o.service = &HuaweiOBSService{client: client}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (o *HuaweiOBS) parseMetadata(metadata bindings.Metadata) (*obsMetadata, error) {
 | 
						|
	var m obsMetadata
 | 
						|
	err := mapstructure.WeakDecode(metadata.Properties, &m)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	if m.Bucket == "" {
 | 
						|
		return nil, fmt.Errorf("missing obs bucket name")
 | 
						|
	}
 | 
						|
	if m.Endpoint == "" {
 | 
						|
		return nil, fmt.Errorf("missing obs endpoint")
 | 
						|
	}
 | 
						|
	if m.AccessKey == "" {
 | 
						|
		return nil, fmt.Errorf("missing the huawei access key")
 | 
						|
	}
 | 
						|
	if m.SecretKey == "" {
 | 
						|
		return nil, fmt.Errorf("missing the huawei secret key")
 | 
						|
	}
 | 
						|
 | 
						|
	o.logger.Debugf("Huawei OBS metadata=[%s]", m)
 | 
						|
	return &m, nil
 | 
						|
}
 | 
						|
 | 
						|
func (o *HuaweiOBS) Operations() []bindings.OperationKind {
 | 
						|
	return []bindings.OperationKind{
 | 
						|
		bindings.CreateOperation,
 | 
						|
		UploadOperation,
 | 
						|
		bindings.GetOperation,
 | 
						|
		bindings.DeleteOperation,
 | 
						|
		bindings.ListOperation,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (o *HuaweiOBS) create(ctx context.Context, req *bindings.InvokeRequest) (*bindings.InvokeResponse, error) {
 | 
						|
	d, err := strconv.Unquote(string(req.Data))
 | 
						|
	if err == nil {
 | 
						|
		req.Data = []byte(d)
 | 
						|
	}
 | 
						|
 | 
						|
	var key string
 | 
						|
	if val, ok := req.Metadata[metadataKey]; ok && val != "" {
 | 
						|
		key = val
 | 
						|
	} else {
 | 
						|
		key = uuid.New().String()
 | 
						|
		o.logger.Debugf("key not found. generating key %s", key)
 | 
						|
	}
 | 
						|
 | 
						|
	r := bytes.NewReader(req.Data)
 | 
						|
 | 
						|
	input := &obs.PutObjectInput{}
 | 
						|
	input.Key = key
 | 
						|
	input.Bucket = o.metadata.Bucket
 | 
						|
	input.Body = r
 | 
						|
 | 
						|
	out, err := o.service.PutObject(ctx, input)
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("obs binding error. putobject: %w", err)
 | 
						|
	}
 | 
						|
 | 
						|
	jsonResponse, err := json.Marshal(createResponse{
 | 
						|
		StatusCode: out.StatusCode,
 | 
						|
		VersionID:  out.VersionId,
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("obs binding error. error marshalling create response: %w", err)
 | 
						|
	}
 | 
						|
 | 
						|
	return &bindings.InvokeResponse{
 | 
						|
		Data: jsonResponse,
 | 
						|
	}, nil
 | 
						|
}
 | 
						|
 | 
						|
func (o *HuaweiOBS) upload(ctx context.Context, req *bindings.InvokeRequest) (*bindings.InvokeResponse, error) {
 | 
						|
	var payload uploadPayload
 | 
						|
	err := json.Unmarshal(req.Data, &payload)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	var key string
 | 
						|
	if val, ok := req.Metadata[metadataKey]; ok && val != "" {
 | 
						|
		key = val
 | 
						|
	} else {
 | 
						|
		key = uuid.New().String()
 | 
						|
		o.logger.Debugf("key not found. generating key %s", key)
 | 
						|
	}
 | 
						|
 | 
						|
	input := &obs.PutFileInput{}
 | 
						|
	input.Key = key
 | 
						|
	input.Bucket = o.metadata.Bucket
 | 
						|
	input.SourceFile = payload.SourceFile
 | 
						|
 | 
						|
	out, err := o.service.PutFile(ctx, input)
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("obs binding error. putfile: %w", err)
 | 
						|
	}
 | 
						|
 | 
						|
	jsonResponse, err := json.Marshal(createResponse{
 | 
						|
		StatusCode: out.StatusCode,
 | 
						|
		VersionID:  out.VersionId,
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("obs binding error. error marshalling create response: %w", err)
 | 
						|
	}
 | 
						|
 | 
						|
	return &bindings.InvokeResponse{
 | 
						|
		Data: jsonResponse,
 | 
						|
	}, nil
 | 
						|
}
 | 
						|
 | 
						|
func (o *HuaweiOBS) get(ctx context.Context, req *bindings.InvokeRequest) (*bindings.InvokeResponse, error) {
 | 
						|
	var key string
 | 
						|
	if val, ok := req.Metadata[metadataKey]; ok && val != "" {
 | 
						|
		key = val
 | 
						|
	} else {
 | 
						|
		return nil, fmt.Errorf("obs binding error: can't read key value")
 | 
						|
	}
 | 
						|
 | 
						|
	input := &obs.GetObjectInput{}
 | 
						|
	input.Bucket = o.metadata.Bucket
 | 
						|
	input.Key = key
 | 
						|
 | 
						|
	out, err := o.service.GetObject(ctx, input)
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("obs binding error. error getting obs object: %w", err)
 | 
						|
	}
 | 
						|
 | 
						|
	// close connection at the end of operation
 | 
						|
	defer func() {
 | 
						|
		err = out.Body.Close()
 | 
						|
		if err != nil {
 | 
						|
			panic(err)
 | 
						|
		}
 | 
						|
	}()
 | 
						|
 | 
						|
	data, err := io.ReadAll(out.Body)
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("obs binding error. error reading obs object content: %w", err)
 | 
						|
	}
 | 
						|
 | 
						|
	return &bindings.InvokeResponse{
 | 
						|
		Data:     data,
 | 
						|
		Metadata: nil,
 | 
						|
	}, nil
 | 
						|
}
 | 
						|
 | 
						|
func (o *HuaweiOBS) delete(ctx context.Context, req *bindings.InvokeRequest) (*bindings.InvokeResponse, error) {
 | 
						|
	var key string
 | 
						|
	if val, ok := req.Metadata[metadataKey]; ok && val != "" {
 | 
						|
		key = val
 | 
						|
	} else {
 | 
						|
		return nil, fmt.Errorf("obs binding error: can't read key value")
 | 
						|
	}
 | 
						|
 | 
						|
	input := &obs.DeleteObjectInput{}
 | 
						|
	input.Bucket = o.metadata.Bucket
 | 
						|
	input.Key = key
 | 
						|
 | 
						|
	out, err := o.service.DeleteObject(ctx, input)
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("obs binding error. error deleting obs object: %w", err)
 | 
						|
	}
 | 
						|
 | 
						|
	jsonResponse, err := json.Marshal(createResponse{
 | 
						|
		StatusCode: out.StatusCode,
 | 
						|
		VersionID:  out.VersionId,
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("obs binding error. error marshalling create response: %w", err)
 | 
						|
	}
 | 
						|
 | 
						|
	return &bindings.InvokeResponse{
 | 
						|
		Data: jsonResponse,
 | 
						|
	}, nil
 | 
						|
}
 | 
						|
 | 
						|
func (o *HuaweiOBS) list(ctx context.Context, req *bindings.InvokeRequest) (*bindings.InvokeResponse, error) {
 | 
						|
	var payload listPayload
 | 
						|
	err := json.Unmarshal(req.Data, &payload)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	if payload.MaxResults == int32(0) {
 | 
						|
		payload.MaxResults = maxResults
 | 
						|
	}
 | 
						|
 | 
						|
	input := &obs.ListObjectsInput{}
 | 
						|
 | 
						|
	input.Bucket = o.metadata.Bucket
 | 
						|
	input.MaxKeys = int(payload.MaxResults)
 | 
						|
	input.Marker = payload.Marker
 | 
						|
	input.Prefix = payload.Prefix
 | 
						|
	input.Delimiter = payload.Delimiter
 | 
						|
 | 
						|
	out, err := o.service.ListObjects(ctx, input)
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("obs binding error. error listing obs objects: %w", err)
 | 
						|
	}
 | 
						|
 | 
						|
	jsonResponse, err := json.Marshal(out)
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("obs binding error. list operation. cannot marshal response to json: %w", err)
 | 
						|
	}
 | 
						|
 | 
						|
	return &bindings.InvokeResponse{
 | 
						|
		Data: jsonResponse,
 | 
						|
	}, nil
 | 
						|
}
 | 
						|
 | 
						|
func (o *HuaweiOBS) Invoke(ctx context.Context, req *bindings.InvokeRequest) (*bindings.InvokeResponse, error) {
 | 
						|
	switch req.Operation {
 | 
						|
	case bindings.CreateOperation:
 | 
						|
		return o.create(ctx, req)
 | 
						|
	case UploadOperation:
 | 
						|
		return o.upload(ctx, req)
 | 
						|
	case bindings.GetOperation:
 | 
						|
		return o.get(ctx, req)
 | 
						|
	case bindings.DeleteOperation:
 | 
						|
		return o.delete(ctx, req)
 | 
						|
	case bindings.ListOperation:
 | 
						|
		return o.list(ctx, req)
 | 
						|
	default:
 | 
						|
		return nil, fmt.Errorf("obs binding error. unsupported operation %s", req.Operation)
 | 
						|
	}
 | 
						|
}
 |