@@ -3,11 +3,17 @@ package deploy
3
3
import (
4
4
"bytes"
5
5
"context"
6
+ "crypto/sha256"
7
+ "encoding/hex"
8
+ "encoding/json"
6
9
"fmt"
10
+ "log"
7
11
"os"
8
12
"path/filepath"
9
13
"strings"
14
+ "sync"
10
15
16
+ ktfs "github.com/ethereum-optimism/optimism/devnet-sdk/kt/fs"
11
17
"github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis"
12
18
"github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/util"
13
19
)
@@ -25,17 +31,43 @@ func (f *FileServer) URL(path ...string) string {
25
31
return fmt .Sprintf ("http://%s/%s" , FILESERVER_PACKAGE , strings .Join (path , "/" ))
26
32
}
27
33
28
- func (f * FileServer ) Deploy (ctx context.Context , sourceDir string ) error {
34
+ func (f * FileServer ) Deploy (ctx context.Context , sourceDir string , stateCh <- chan * fileserverState ) error {
35
+ // Check if source directory is empty. If it is, then ie means we don't have
36
+ // anything to serve, so we might as well not deploy the fileserver.
37
+ entries , err := os .ReadDir (sourceDir )
38
+ if err != nil {
39
+ return fmt .Errorf ("error reading source directory: %w" , err )
40
+ }
41
+ if len (entries ) == 0 {
42
+ return nil
43
+ }
44
+
45
+ srcHash , err := calculateDirHash (sourceDir )
46
+ if err != nil {
47
+ return fmt .Errorf ("error calculating source directory hash: %w" , err )
48
+ }
29
49
// Create a temp dir in the fileserver package
30
50
baseDir := filepath .Join (f .baseDir , FILESERVER_PACKAGE )
31
51
if err := os .MkdirAll (baseDir , 0755 ); err != nil {
32
52
return fmt .Errorf ("error creating base directory: %w" , err )
33
53
}
54
+
55
+ configHash , err := calculateDirHash (filepath .Join (baseDir , "static_files" , "nginx" ))
56
+ if err != nil {
57
+ return fmt .Errorf ("error calculating base directory hash: %w" , err )
58
+ }
59
+
60
+ refState := <- stateCh
61
+ if refState .contentHash == srcHash && refState .configHash == configHash {
62
+ log .Println ("No changes to fileserver, skipping deployment" )
63
+ return nil
64
+ }
65
+
34
66
// Can't use MkdirTemp here because the directory name needs to always be the same
35
67
// in order for kurtosis file artifact upload to be idempotent.
36
68
// (i.e. the file upload and all its downstream dependencies can be SKIPPED on re-runs)
37
69
tempDir := filepath .Join (baseDir , "upload-content" )
38
- err : = os .Mkdir (tempDir , 0755 )
70
+ err = os .Mkdir (tempDir , 0755 )
39
71
if err != nil {
40
72
return fmt .Errorf ("error creating temporary directory: %w" , err )
41
73
}
@@ -68,3 +100,145 @@ func (f *FileServer) Deploy(ctx context.Context, sourceDir string) error {
68
100
69
101
return nil
70
102
}
103
+
104
+ type fileserverState struct {
105
+ contentHash string
106
+ configHash string
107
+ }
108
+
109
+ // downloadAndHashArtifact downloads an artifact and calculates its hash
110
+ func downloadAndHashArtifact (ctx context.Context , enclave , artifactName string ) (string , error ) {
111
+ fs , err := ktfs .NewEnclaveFS (ctx , enclave )
112
+ if err != nil {
113
+ return "" , fmt .Errorf ("failed to create enclave fs: %w" , err )
114
+ }
115
+
116
+ // Create temp dir
117
+ tempDir , err := os .MkdirTemp ("" , artifactName + "-*" )
118
+ if err != nil {
119
+ return "" , fmt .Errorf ("failed to create temp dir: %w" , err )
120
+ }
121
+ defer os .RemoveAll (tempDir )
122
+
123
+ // Download artifact
124
+ artifact , err := fs .GetArtifact (ctx , artifactName )
125
+ if err != nil {
126
+ return "" , fmt .Errorf ("failed to get artifact: %w" , err )
127
+ }
128
+
129
+ // Ensure parent directories exist before extracting
130
+ if err := os .MkdirAll (tempDir , 0755 ); err != nil {
131
+ return "" , fmt .Errorf ("failed to create temp dir structure: %w" , err )
132
+ }
133
+
134
+ // Extract to temp dir
135
+ if err := artifact .Download (tempDir ); err != nil {
136
+ return "" , fmt .Errorf ("failed to download artifact: %w" , err )
137
+ }
138
+
139
+ // Calculate hash
140
+ hash , err := calculateDirHash (tempDir )
141
+ if err != nil {
142
+ return "" , fmt .Errorf ("failed to calculate hash: %w" , err )
143
+ }
144
+
145
+ return hash , nil
146
+ }
147
+
148
+ func (f * FileServer ) getState (ctx context.Context ) <- chan * fileserverState {
149
+ stateCh := make (chan * fileserverState )
150
+
151
+ go func (ctx context.Context ) {
152
+ st := & fileserverState {}
153
+ var wg sync.WaitGroup
154
+
155
+ type artifactInfo struct {
156
+ name string
157
+ dest * string
158
+ }
159
+
160
+ artifacts := []artifactInfo {
161
+ {"fileserver-content" , & st .contentHash },
162
+ {"fileserver-nginx-conf" , & st .configHash },
163
+ }
164
+
165
+ for _ , art := range artifacts {
166
+ wg .Add (1 )
167
+ go func (art artifactInfo ) {
168
+ defer wg .Done ()
169
+ hash , err := downloadAndHashArtifact (ctx , f .enclave , art .name )
170
+ if err == nil {
171
+ * art .dest = hash
172
+ }
173
+ }(art )
174
+ }
175
+
176
+ wg .Wait ()
177
+ stateCh <- st
178
+ }(ctx )
179
+
180
+ return stateCh
181
+ }
182
+
183
+ type entry struct {
184
+ RelPath string `json:"rel_path"`
185
+ Size int64 `json:"size"`
186
+ Mode string `json:"mode"`
187
+ Content []byte `json:"content"`
188
+ }
189
+
190
+ // calculateDirHash returns a SHA256 hash of the directory contents
191
+ // It walks through the directory, hashing file names and contents
192
+ func calculateDirHash (dir string ) (string , error ) {
193
+ hash := sha256 .New ()
194
+ err := filepath .Walk (dir , func (path string , info os.FileInfo , err error ) error {
195
+ if err != nil {
196
+ return err
197
+ }
198
+
199
+ // Get path relative to root dir
200
+ relPath , err := filepath .Rel (dir , path )
201
+ if err != nil {
202
+ return err
203
+ }
204
+
205
+ // Skip the root directory
206
+ if relPath == "." {
207
+ return nil
208
+ }
209
+
210
+ // Add the relative path and file info to hash
211
+ entry := entry {
212
+ RelPath : relPath ,
213
+ Size : info .Size (),
214
+ Mode : info .Mode ().String (),
215
+ }
216
+
217
+ // If it's a regular file, add its contents to hash
218
+ if ! info .IsDir () {
219
+ content , err := os .ReadFile (path )
220
+ if err != nil {
221
+ return err
222
+ }
223
+ entry .Content = content
224
+ }
225
+
226
+ jsonBytes , err := json .Marshal (entry )
227
+ if err != nil {
228
+ return err
229
+ }
230
+ _ , err = hash .Write (jsonBytes )
231
+ if err != nil {
232
+ return err
233
+ }
234
+
235
+ return nil
236
+ })
237
+
238
+ if err != nil {
239
+ return "" , fmt .Errorf ("error walking directory: %w" , err )
240
+ }
241
+
242
+ hashStr := hex .EncodeToString (hash .Sum (nil ))
243
+ return hashStr , nil
244
+ }
0 commit comments