1
- // Copyright 2020 The Cloudprober Authors.
1
+ // Copyright 2020-2023 The Cloudprober Authors.
2
2
//
3
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
4
// you may not use this file except in compliance with the License.
@@ -19,16 +19,13 @@ package file
19
19
20
20
import (
21
21
"context"
22
- "errors"
23
22
"fmt"
24
23
"io"
25
24
"net/http"
26
25
"os"
27
26
"strings"
28
27
"sync"
29
28
"time"
30
-
31
- "golang.org/x/oauth2/google"
32
29
)
33
30
34
31
type cacheEntry struct {
@@ -43,64 +40,99 @@ var global = struct {
43
40
cache : make (map [string ]cacheEntry ),
44
41
}
45
42
46
- type readFunc func (path string ) ([]byte , error )
47
- type modTimeFunc func (path string ) (time.Time , error )
43
+ type readFunc func (ctx context.Context , path string ) ([]byte , error )
44
+ type modTimeFunc func (ctx context.Context , path string ) (time.Time , error )
45
+
46
+ var zeroTime = time.Time {}
48
47
49
48
var prefixToReadfunc = map [string ]readFunc {
50
- "gs://" : readFileFromGCS ,
49
+ "gs://" : readFileFromGCS ,
50
+ "s3://" : readFileFromS3 ,
51
+ "http://" : readFileFromHTTP ,
52
+ "https://" : readFileFromHTTP ,
51
53
}
52
54
53
55
var prefixToModTimeFunc = map [string ]modTimeFunc {
54
- "gs://" : modTimeGCS ,
56
+ "gs://" : gcsModTime ,
57
+ "s3://" : s3ModTime ,
58
+ "http://" : httpModTime ,
59
+ "https://" : httpModTime ,
55
60
}
56
61
57
- func readFileFromGCS (objectPath string ) ([] byte , error ) {
58
- hc , err := google . DefaultClient ( context . Background () )
59
- if err != nil {
60
- return nil , err
62
+ func parseObjectURL (objectPath string ) (bucket , object string , err error ) {
63
+ parts := strings . SplitN ( objectPath , "/" , 2 )
64
+ if len ( parts ) != 2 {
65
+ return "" , "" , fmt . Errorf ( "invalid object URL: %s" , objectPath )
61
66
}
67
+ return parts [0 ], parts [1 ], nil
68
+ }
62
69
63
- objURL := "https://storage.googleapis.com/" + objectPath
64
- res , err := hc .Get (objURL )
70
+ func httpLastModified (res * http.Response ) (time.Time , error ) {
71
+ t , err := time .Parse (time .RFC1123 , res .Header .Get ("Last-Modified" ))
72
+ if err != nil {
73
+ return zeroTime , fmt .Errorf ("error parsing Last-Modified header: %v" , err )
74
+ }
75
+ return t , nil
76
+ }
65
77
78
+ func readFileFromHTTP (ctx context.Context , fileURL string ) ([]byte , error ) {
79
+ req , err := http .NewRequestWithContext (ctx , "GET" , fileURL , nil )
80
+ if err != nil {
81
+ return nil , err
82
+ }
83
+ res , err := http .DefaultClient .Do (req )
66
84
if err != nil {
67
85
return nil , err
68
86
}
69
87
70
88
if res .StatusCode != http .StatusOK {
71
- return nil , fmt .Errorf ("got error while retrieving GCS object, http status: %s, status code: %d" , res .Status , res .StatusCode )
89
+ return nil , fmt .Errorf ("got error while retrieving HTTP object, http status: %s, status code: %d" , res .Status , res .StatusCode )
72
90
}
73
91
74
92
defer res .Body .Close ()
75
93
return io .ReadAll (res .Body )
76
94
}
77
95
78
- func modTimeGCS (objectPath string ) (time.Time , error ) {
79
- return time.Time {}, errors .New ("mod-time is not implemented for GCS files yet" )
96
+ func httpModTime (ctx context.Context , fileURL string ) (time.Time , error ) {
97
+ req , err := http .NewRequestWithContext (ctx , "HEAD" , fileURL , nil )
98
+ if err != nil {
99
+ return zeroTime , err
100
+ }
101
+ res , err := http .DefaultClient .Do (req )
102
+ if err != nil {
103
+ return zeroTime , err
104
+ }
105
+
106
+ if res .StatusCode != http .StatusOK {
107
+ return zeroTime , fmt .Errorf ("got error while retrieving HTTP object, http status: %s, status code: %d" , res .Status , res .StatusCode )
108
+ }
109
+
110
+ defer res .Body .Close ()
111
+ return httpLastModified (res )
80
112
}
81
113
82
114
// ReadFile returns file contents as a slice of bytes. It's similar to ioutil's
83
115
// ReadFile, but includes support for files on non-disk locations. For example,
84
116
// files with paths starting with gs:// are assumed to be on GCS, and are read
85
117
// from GCS.
86
- func ReadFile (fname string ) ([]byte , error ) {
118
+ func ReadFile (ctx context. Context , fname string ) ([]byte , error ) {
87
119
for prefix , f := range prefixToReadfunc {
88
120
if strings .HasPrefix (fname , prefix ) {
89
- return f (fname [len (prefix ):])
121
+ return f (ctx , fname [len (prefix ):])
90
122
}
91
123
}
92
124
return os .ReadFile (fname )
93
125
}
94
126
95
- func ReadWithCache (fname string , refreshInterval time.Duration ) ([]byte , error ) {
127
+ func ReadWithCache (ctx context. Context , fname string , refreshInterval time.Duration ) ([]byte , error ) {
96
128
global .mu .RLock ()
97
129
fc , ok := global .cache [fname ]
98
130
global .mu .RUnlock ()
99
131
if ok && (time .Since (fc .lastReload ) < refreshInterval ) {
100
132
return fc .b , nil
101
133
}
102
134
103
- b , err := ReadFile (fname )
135
+ b , err := ReadFile (ctx , fname )
104
136
if err != nil {
105
137
return nil , err
106
138
}
@@ -112,10 +144,10 @@ func ReadWithCache(fname string, refreshInterval time.Duration) ([]byte, error)
112
144
}
113
145
114
146
// ModTime returns file's modified timestamp.
115
- func ModTime (fname string ) (time.Time , error ) {
147
+ func ModTime (ctx context. Context , fname string ) (time.Time , error ) {
116
148
for prefix , f := range prefixToModTimeFunc {
117
149
if strings .HasPrefix (fname , prefix ) {
118
- return f (fname [len (prefix ):])
150
+ return f (ctx , fname [len (prefix ):])
119
151
}
120
152
}
121
153
0 commit comments