github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/pkg/io/providers/providers.go (about) 1 /* 2 Copyright 2020 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package providers 18 19 import ( 20 "context" 21 "encoding/json" 22 "fmt" 23 "net/url" 24 "strings" 25 26 "github.com/aws/aws-sdk-go/aws" 27 "github.com/aws/aws-sdk-go/aws/credentials" 28 "github.com/aws/aws-sdk-go/aws/session" 29 "gocloud.dev/blob" 30 _ "gocloud.dev/blob/memblob" 31 "gocloud.dev/blob/s3blob" 32 33 prowv1 "sigs.k8s.io/prow/pkg/apis/prowjobs/v1" 34 ) 35 36 const ( 37 S3 = "s3" 38 GS = "gs" 39 // TODO(danilo-gemoli): complete the implementation since at this time only opener.Writer() 40 // is supported 41 File = "file" 42 ) 43 44 // DisplayName turns canonical provider IDs into displayable names. 45 func DisplayName(provider string) string { 46 switch provider { 47 case GS: 48 return "GCS" 49 case S3: 50 return "S3" 51 case File: 52 return "File" 53 } 54 return provider 55 } 56 57 // GetBucket opens and returns a gocloud blob.Bucket based on credentials and a path. 58 // The path is used to discover which storageProvider should be used. 59 // 60 // If the storageProvider file is detected, we don't need any credentials and just open a file bucket 61 // If no credentials are given, we just fall back to blob.OpenBucket which tries to auto discover credentials 62 // e.g. via environment variables. For more details, see: https://gocloud.dev/howto/blob/ 63 // 64 // If we specify credentials and an 3:// path is used, credentials must be given in one of the 65 // following formats: 66 // - AWS S3 (s3://): 67 // { 68 // "region": "us-east-1", 69 // "s3_force_path_style": true, 70 // "access_key": "access_key", 71 // "secret_key": "secret_key" 72 // } 73 // - S3-compatible service, e.g. self-hosted Minio (s3://): 74 // { 75 // "region": "minio", 76 // "endpoint": "https://minio-hl-svc.minio-operator-ns:9000", 77 // "s3_force_path_style": true, 78 // "access_key": "access_key", 79 // "secret_key": "secret_key" 80 // } 81 func GetBucket(ctx context.Context, s3Credentials []byte, path string) (*blob.Bucket, error) { 82 storageProvider, bucket, _, err := ParseStoragePath(path) 83 if err != nil { 84 return nil, err 85 } 86 if storageProvider == S3 && len(s3Credentials) > 0 { 87 return getS3Bucket(ctx, s3Credentials, bucket) 88 } 89 90 bkt, err := blob.OpenBucket(ctx, fmt.Sprintf("%s://%s", storageProvider, bucket)) 91 if err != nil { 92 return nil, fmt.Errorf("error opening file bucket: %w", err) 93 } 94 return bkt, nil 95 } 96 97 // s3Credentials are credentials used to access S3 or an S3-compatible storage service 98 // Endpoint is an optional property. Default is the AWS S3 endpoint. If set, the specified 99 // endpoint will be used instead. 100 type s3Credentials struct { 101 Region string `json:"region"` 102 Endpoint string `json:"endpoint"` 103 Insecure bool `json:"insecure"` 104 S3ForcePathStyle bool `json:"s3_force_path_style"` 105 AccessKey string `json:"access_key"` 106 SecretKey string `json:"secret_key"` 107 } 108 109 // getS3Bucket opens a gocloud blob.Bucket based on given credentials in the format the 110 // struct s3Credentials defines (see documentation of GetBucket for an example) 111 func getS3Bucket(ctx context.Context, creds []byte, bucketName string) (*blob.Bucket, error) { 112 s3Creds := &s3Credentials{} 113 if err := json.Unmarshal(creds, s3Creds); err != nil { 114 return nil, fmt.Errorf("error getting S3 credentials from JSON: %w", err) 115 } 116 117 cfg := &aws.Config{} 118 119 // Use the default credential chain if no credentials are specified 120 if s3Creds.AccessKey != "" && s3Creds.SecretKey != "" { 121 staticCredentials := credentials.StaticProvider{ 122 Value: credentials.Value{ 123 AccessKeyID: s3Creds.AccessKey, 124 SecretAccessKey: s3Creds.SecretKey, 125 }, 126 } 127 128 cfg.Credentials = credentials.NewChainCredentials([]credentials.Provider{&staticCredentials}) 129 } 130 131 cfg.Endpoint = aws.String(s3Creds.Endpoint) 132 cfg.DisableSSL = aws.Bool(s3Creds.Insecure) 133 cfg.S3ForcePathStyle = aws.Bool(s3Creds.S3ForcePathStyle) 134 cfg.Region = aws.String(s3Creds.Region) 135 136 sess, err := session.NewSession(cfg) 137 if err != nil { 138 return nil, fmt.Errorf("error creating S3 Session: %w", err) 139 } 140 141 bkt, err := s3blob.OpenBucket(ctx, sess, bucketName, nil) 142 if err != nil { 143 return nil, fmt.Errorf("error opening S3 bucket: %w", err) 144 } 145 return bkt, nil 146 } 147 148 // HasStorageProviderPrefix returns true if the given string starts with 149 // any of the known storageProviders and a slash, e.g. 150 // * gs/kubernetes-jenkins returns true 151 // * kubernetes-jenkins returns false 152 func HasStorageProviderPrefix(path string) bool { 153 return strings.HasPrefix(path, GS+"/") || strings.HasPrefix(path, S3+"/") 154 } 155 156 // ParseStoragePath parses storagePath and returns the storageProvider, bucket and relativePath 157 // For example gs://prow-artifacts/test.log results in (gs, prow-artifacts, test.log) 158 // Currently detected storageProviders are GS, S3 and file. 159 // Paths with a leading / instead of a storageProvider prefix are treated as file paths for backwards 160 // compatibility reasons. 161 // File paths are split into a directory and a file. Directory is returned as bucket, file is returned. 162 // as relativePath. 163 // For all other paths the first part is treated as storageProvider prefix, the second segment as bucket 164 // and everything after the bucket as relativePath. 165 func ParseStoragePath(storagePath string) (storageProvider, bucket, relativePath string, err error) { 166 parsedPath, err := url.Parse(storagePath) 167 if err != nil { 168 return "", "", "", fmt.Errorf("unable to parse path %q: %w", storagePath, err) 169 } 170 171 storageProvider = parsedPath.Scheme 172 bucket, relativePath = parsedPath.Host, parsedPath.Path 173 relativePath = strings.TrimPrefix(relativePath, "/") 174 175 if bucket == "" { 176 return "", "", "", fmt.Errorf("could not find bucket in storagePath %q", storagePath) 177 } 178 return storageProvider, bucket, relativePath, nil 179 } 180 181 // StoragePath is the reverse of ParseStoragePath. 182 func StoragePath(bucket, path string) (string, error) { 183 pp, err := prowv1.ParsePath(bucket) 184 if err != nil { 185 return "", err 186 } 187 return fmt.Sprintf("%s://%s/%s", pp.StorageProvider(), pp.Bucket(), path), nil 188 }