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 }