github.com/jaylevin/jenkins-library@v1.230.4/pkg/gcs/gcsClient.go (about)

     1  package gcs
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"os"
     7  	"path/filepath"
     8  
     9  	"cloud.google.com/go/storage"
    10  	"github.com/SAP/jenkins-library/pkg/log"
    11  	"github.com/pkg/errors"
    12  	"google.golang.org/api/iterator"
    13  	"google.golang.org/api/option"
    14  )
    15  
    16  // Client is an interface to mock gcsClient
    17  type Client interface {
    18  	UploadFile(bucketID string, sourcePath string, targetPath string) error
    19  	DownloadFile(bucketID string, sourcePath string, targetPath string) error
    20  	ListFiles(bucketID string) ([]string, error)
    21  	Close() error
    22  }
    23  
    24  // gcsClient provides functions to interact with google cloud storage API
    25  type gcsClient struct {
    26  	context       context.Context
    27  	envVars       []EnvVar
    28  	client        storage.Client
    29  	clientOptions []option.ClientOption
    30  	openFile      func(name string) (io.ReadCloser, error)
    31  	createFile    func(name string) (io.WriteCloser, error)
    32  }
    33  
    34  // EnvVar defines an  environment variable incl. information about a potential modification to the variable
    35  type EnvVar struct {
    36  	Name     string
    37  	Value    string
    38  	Modified bool
    39  }
    40  
    41  type gcsOption func(*gcsClient)
    42  
    43  // WithEnvVars initializes env variables in gcsClient
    44  func WithEnvVars(envVars []EnvVar) gcsOption {
    45  	return func(g *gcsClient) {
    46  		g.envVars = envVars
    47  	}
    48  }
    49  
    50  // WithEnvVars initializes the openFile function in gcsClient
    51  func WithOpenFileFunction(openFile func(name string) (io.ReadCloser, error)) gcsOption {
    52  	return func(g *gcsClient) {
    53  		g.openFile = openFile
    54  	}
    55  }
    56  
    57  // WithEnvVars initializes the createFile function in gcsClient
    58  func WithCreateFileFunction(createFile func(name string) (io.WriteCloser, error)) gcsOption {
    59  	return func(g *gcsClient) {
    60  		g.createFile = createFile
    61  	}
    62  }
    63  
    64  // WithEnvVars initializes the Google Cloud Storage client options
    65  func WithClientOptions(opts ...option.ClientOption) gcsOption {
    66  	return func(g *gcsClient) {
    67  		g.clientOptions = append(g.clientOptions, opts...)
    68  	}
    69  }
    70  
    71  // Init intitializes the google cloud storage client
    72  func NewClient(opts ...gcsOption) (*gcsClient, error) {
    73  	var (
    74  		defaultOpenFile   = openFileFromFS
    75  		defaultCreateFile = createFileOnFS
    76  	)
    77  
    78  	ctx := context.Background()
    79  	gcsClient := &gcsClient{
    80  		context:    ctx,
    81  		openFile:   defaultOpenFile,
    82  		createFile: defaultCreateFile,
    83  	}
    84  
    85  	// options handling
    86  	for _, opt := range opts {
    87  		opt(gcsClient)
    88  	}
    89  
    90  	gcsClient.prepareEnv()
    91  	client, err := storage.NewClient(ctx, gcsClient.clientOptions...)
    92  	if err != nil {
    93  		return nil, errors.Wrapf(err, "bucket connection failed: %v", err)
    94  	}
    95  	gcsClient.client = *client
    96  	return gcsClient, nil
    97  }
    98  
    99  // UploadFile uploads a file into a google cloud storage bucket
   100  func (g *gcsClient) UploadFile(bucketID string, sourcePath string, targetPath string) error {
   101  	target := g.client.Bucket(bucketID).Object(targetPath).NewWriter(g.context)
   102  	log.Entry().Debugf("uploading %v to %v", sourcePath, targetPath)
   103  	sourceFile, err := g.openFile(sourcePath)
   104  	if err != nil {
   105  		return errors.Wrapf(err, "could not open source file: %v", err)
   106  	}
   107  	defer sourceFile.Close()
   108  
   109  	if err := g.copy(sourceFile, target); err != nil {
   110  		return errors.Wrapf(err, "upload failed: %v", err)
   111  	}
   112  
   113  	if err := target.Close(); err != nil {
   114  		return errors.Wrapf(err, "closing bucket failed: %v", err)
   115  	}
   116  	return nil
   117  }
   118  
   119  // DownloadFile downloads a file from a google cloud storage bucket
   120  func (g *gcsClient) DownloadFile(bucketID string, sourcePath string, targetPath string) error {
   121  	log.Entry().Debugf("downloading %v to %v\n", sourcePath, targetPath)
   122  	gcsReader, err := g.client.Bucket(bucketID).Object(sourcePath).NewReader(g.context)
   123  	if err != nil {
   124  		return errors.Wrapf(err, "could not open source file from a google cloud storage bucket: %v", err)
   125  	}
   126  
   127  	targetWriter, err := g.createFile(targetPath)
   128  	if err != nil {
   129  		return errors.Wrapf(err, "could not create target file: %v", err)
   130  	}
   131  	defer targetWriter.Close()
   132  
   133  	if err := g.copy(gcsReader, targetWriter); err != nil {
   134  		return errors.Wrapf(err, "download failed: %v", err)
   135  	}
   136  	if err := gcsReader.Close(); err != nil {
   137  		return errors.Wrapf(err, "closing bucket failed: %v", err)
   138  	}
   139  	return nil
   140  }
   141  
   142  // ListFiles lists all files in certain GCS bucket
   143  func (g *gcsClient) ListFiles(bucketID string) ([]string, error) {
   144  	fileNames := []string{}
   145  	it := g.client.Bucket(bucketID).Objects(g.context, nil)
   146  	for {
   147  		attrs, err := it.Next()
   148  		if err == iterator.Done {
   149  			break
   150  		}
   151  		if err != nil {
   152  			return nil, err
   153  		}
   154  		fileNames = append(fileNames, attrs.Name)
   155  	}
   156  	return fileNames, nil
   157  }
   158  
   159  // Close closes the client and removes previously set environment variables
   160  func (g *gcsClient) Close() error {
   161  	if err := g.client.Close(); err != nil {
   162  		return err
   163  	}
   164  	if err := g.cleanupEnv(); err != nil {
   165  		return err
   166  	}
   167  	return nil
   168  }
   169  
   170  func (g *gcsClient) copy(source io.Reader, target io.Writer) error {
   171  	if _, err := io.Copy(target, source); err != nil {
   172  		return err
   173  	}
   174  	return nil
   175  }
   176  
   177  // prepareEnv sets required environment variables in case they are not set yet
   178  func (g *gcsClient) prepareEnv() {
   179  	for key, env := range g.envVars {
   180  		g.envVars[key].Modified = setenvIfEmpty(env.Name, env.Value)
   181  	}
   182  }
   183  
   184  // cleanupEnv removes environment variables set by prepareEnv
   185  func (g *gcsClient) cleanupEnv() error {
   186  	for _, env := range g.envVars {
   187  		if err := removeEnvIfPreviouslySet(env.Name, env.Modified); err != nil {
   188  			return err
   189  		}
   190  	}
   191  	return nil
   192  }
   193  
   194  func setenvIfEmpty(env, val string) bool {
   195  	if len(os.Getenv(env)) == 0 {
   196  		os.Setenv(env, val)
   197  		return true
   198  	}
   199  	return false
   200  }
   201  
   202  func removeEnvIfPreviouslySet(env string, previouslySet bool) error {
   203  	if previouslySet {
   204  		if err := os.Setenv(env, ""); err != nil {
   205  			return err
   206  		}
   207  	}
   208  	return nil
   209  }
   210  
   211  func openFileFromFS(name string) (io.ReadCloser, error) {
   212  	return os.Open(name)
   213  }
   214  
   215  func createFileOnFS(name string) (io.WriteCloser, error) {
   216  	if err := os.MkdirAll(filepath.Dir(name), os.ModePerm); err != nil {
   217  		return nil, err
   218  	}
   219  	return os.Create(name)
   220  }