github.com/qiniu/go-sdk/v7@v7.27.0/examples/sandbox_envd/main.go (about) 1 package main 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "log" 8 "os" 9 "time" 10 11 "github.com/qiniu/go-sdk/v7/sandbox" 12 ) 13 14 func main() { 15 apiKey := os.Getenv("QINIU_API_KEY") 16 if apiKey == "" { 17 log.Fatal("请设置 QINIU_API_KEY 环境变量") 18 } 19 20 apiURL := os.Getenv("QINIU_SANDBOX_API_URL") 21 22 c, err := sandbox.NewClient(&sandbox.Config{ 23 APIKey: apiKey, 24 Endpoint: apiURL, 25 }) 26 if err != nil { 27 log.Fatalf("创建客户端失败: %v", err) 28 } 29 30 ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) 31 defer cancel() 32 33 // 1. 列出模板,选取第一个可用模板 34 templates, err := c.ListTemplates(ctx, nil) 35 if err != nil { 36 log.Fatalf("列出模板失败: %v", err) 37 } 38 39 var templateID string 40 for _, tmpl := range templates { 41 if tmpl.BuildStatus == sandbox.BuildStatusReady || tmpl.BuildStatus == sandbox.BuildStatusUploaded { 42 templateID = tmpl.TemplateID 43 break 44 } 45 } 46 if templateID == "" { 47 log.Fatal("没有构建成功的模板") 48 } 49 fmt.Printf("使用模板: %s\n", templateID) 50 51 // 2. 创建沙箱并等待就绪(附带 Metadata 和 NetworkConfig) 52 timeout := int32(120) 53 meta := sandbox.Metadata{"env": "dev", "team": "backend"} 54 network := sandbox.NetworkConfig{ 55 AllowPublicTraffic: boolPtr(true), 56 } 57 sb, info, err := c.CreateAndWait(ctx, sandbox.CreateParams{ 58 TemplateID: templateID, 59 Timeout: &timeout, 60 Metadata: &meta, 61 Network: &network, 62 }, sandbox.WithPollInterval(2*time.Second)) 63 if err != nil { 64 log.Fatalf("创建沙箱失败: %v", err) 65 } 66 fmt.Printf("沙箱已就绪: %s\n", sb.ID()) 67 68 // 验证 Metadata 69 if info.Metadata != nil { 70 fmt.Printf("Metadata: %v\n", *info.Metadata) 71 } 72 73 defer func() { 74 killCtx, killCancel := context.WithTimeout(context.Background(), 10*time.Second) 75 defer killCancel() 76 if err := sb.Kill(killCtx); err != nil { 77 log.Printf("终止沙箱失败: %v", err) 78 } else { 79 fmt.Printf("沙箱 %s 已终止\n", sb.ID()) 80 } 81 }() 82 83 // 3. 获取端口域名 84 host := sb.GetHost(8080) 85 fmt.Printf("端口 8080 访问地址: %s\n", host) 86 87 // 4. 文件系统操作 88 fmt.Println("\n--- 文件系统操作 ---") 89 90 // 写入文件 91 _, err = sb.Files().Write(ctx, "/tmp/hello.txt", []byte("Hello from Go SDK!\n")) 92 if err != nil { 93 log.Fatalf("写入文件失败: %v", err) 94 } 95 fmt.Println("文件已写入: /tmp/hello.txt") 96 97 // 读取文件 98 content, err := sb.Files().Read(ctx, "/tmp/hello.txt") 99 if err != nil { 100 log.Fatalf("读取文件失败: %v", err) 101 } 102 fmt.Printf("文件内容: %s", string(content)) 103 104 // 创建目录 105 _, err = sb.Files().MakeDir(ctx, "/tmp/mydir") 106 if err != nil { 107 log.Fatalf("创建目录失败: %v", err) 108 } 109 fmt.Println("目录已创建: /tmp/mydir") 110 111 // 列出目录 112 entries, err := sb.Files().List(ctx, "/tmp") 113 if err != nil { 114 log.Fatalf("列出目录失败: %v", err) 115 } 116 fmt.Printf("/tmp 目录内容 (%d 项):\n", len(entries)) 117 for _, e := range entries { 118 fmt.Printf(" %s %s (%s, %d bytes)\n", e.Type, e.Name, e.Permissions, e.Size) 119 } 120 121 // 批量写入文件 122 fmt.Println("\n--- 批量写入文件 ---") 123 files := []sandbox.WriteEntry{ 124 {Path: "/tmp/batch-a.txt", Data: []byte("file A content")}, 125 {Path: "/tmp/batch-b.txt", Data: []byte("file B content")}, 126 {Path: "/tmp/batch-c.txt", Data: []byte("file C content")}, 127 } 128 infos, err := sb.Files().WriteFiles(ctx, files) 129 if err != nil { 130 log.Fatalf("批量写入失败: %v", err) 131 } 132 for _, fi := range infos { 133 fmt.Printf("已写入: %s (%d bytes)\n", fi.Path, fi.Size) 134 } 135 136 // ReadText — 读取文件为字符串 137 fmt.Println("\n--- ReadText ---") 138 text, err := sb.Files().ReadText(ctx, "/tmp/batch-a.txt") 139 if err != nil { 140 log.Fatalf("ReadText 失败: %v", err) 141 } 142 fmt.Printf("ReadText 结果: %q\n", text) 143 144 // ReadStream — 流式读取文件 145 fmt.Println("\n--- ReadStream ---") 146 rc, err := sb.Files().ReadStream(ctx, "/tmp/batch-b.txt") 147 if err != nil { 148 log.Fatalf("ReadStream 失败: %v", err) 149 } 150 streamData, err := io.ReadAll(rc) 151 rc.Close() 152 if err != nil { 153 log.Fatalf("读取流失败: %v", err) 154 } 155 fmt.Printf("ReadStream 结果: %q\n", string(streamData)) 156 157 // Exists — 检查文件是否存在 158 fmt.Println("\n--- Exists / GetInfo / Rename / Remove ---") 159 exists, err := sb.Files().Exists(ctx, "/tmp/batch-a.txt") 160 if err != nil { 161 log.Fatalf("Exists 失败: %v", err) 162 } 163 fmt.Printf("Exists(/tmp/batch-a.txt) = %v\n", exists) 164 165 // GetInfo — 获取文件元信息 166 fileInfo, err := sb.Files().GetInfo(ctx, "/tmp/batch-a.txt") 167 if err != nil { 168 log.Fatalf("GetInfo 失败: %v", err) 169 } 170 fmt.Printf("GetInfo: name=%s, type=%s, size=%d, mode=%s\n", 171 fileInfo.Name, fileInfo.Type, fileInfo.Size, fileInfo.Permissions) 172 173 // Rename — 重命名文件 174 renamedInfo, err := sb.Files().Rename(ctx, "/tmp/batch-c.txt", "/tmp/batch-c-renamed.txt") 175 if err != nil { 176 log.Fatalf("Rename 失败: %v", err) 177 } 178 fmt.Printf("Rename: %s -> %s\n", "/tmp/batch-c.txt", renamedInfo.Path) 179 180 // Remove — 删除文件 181 if err := sb.Files().Remove(ctx, "/tmp/batch-c-renamed.txt"); err != nil { 182 log.Fatalf("Remove 失败: %v", err) 183 } 184 exists, err = sb.Files().Exists(ctx, "/tmp/batch-c-renamed.txt") 185 if err != nil { 186 log.Fatalf("Exists 失败: %v", err) 187 } 188 fmt.Printf("Remove 后 Exists(/tmp/batch-c-renamed.txt) = %v\n", exists) 189 190 // WatchDir — 监听目录文件变更 191 fmt.Println("\n--- WatchDir ---") 192 193 // 创建监听目录 194 _, err = sb.Files().MakeDir(ctx, "/tmp/watch-test") 195 if err != nil { 196 log.Fatalf("创建监听目录失败: %v", err) 197 } 198 199 // 启动目录监听 200 wh, err := sb.Files().WatchDir(ctx, "/tmp/watch-test", sandbox.WithRecursive(true)) 201 if err != nil { 202 log.Fatalf("WatchDir 失败: %v", err) 203 } 204 fmt.Println("已开始监听 /tmp/watch-test(递归)") 205 206 // 在监听目录中触发文件变更 207 _, err = sb.Files().Write(ctx, "/tmp/watch-test/watch-file.txt", []byte("watched content")) 208 if err != nil { 209 log.Fatalf("写入监听文件失败: %v", err) 210 } 211 fmt.Println("已写入: /tmp/watch-test/watch-file.txt") 212 213 // 收集事件(等待最多 3 秒) 214 timer := time.NewTimer(3 * time.Second) 215 defer timer.Stop() 216 eventCount := 0 217 loop: 218 for { 219 select { 220 case ev, ok := <-wh.Events(): 221 if !ok { 222 break loop 223 } 224 eventCount++ 225 fmt.Printf(" 事件: type=%s, name=%s\n", ev.Type, ev.Name) 226 case <-timer.C: 227 break loop 228 } 229 } 230 fmt.Printf("共收到 %d 个事件\n", eventCount) 231 232 // 停止监听 233 wh.Stop() 234 if err := wh.Err(); err != nil { 235 fmt.Printf("监听错误: %v\n", err) 236 } else { 237 fmt.Println("监听已停止") 238 } 239 240 // 5. 执行命令 241 fmt.Println("\n--- 命令执行 ---") 242 243 result, err := sb.Commands().Run(ctx, "echo hello world") 244 if err != nil { 245 log.Fatalf("执行命令失败: %v", err) 246 } 247 fmt.Printf("命令: echo hello world\n") 248 fmt.Printf("退出码: %d\n", result.ExitCode) 249 fmt.Printf("stdout: %s", result.Stdout) 250 251 // 带环境变量的命令 252 result, err = sb.Commands().Run(ctx, "echo $MY_VAR", 253 sandbox.WithEnvs(map[string]string{"MY_VAR": "sandbox-value"}), 254 ) 255 if err != nil { 256 log.Fatalf("执行命令失败: %v", err) 257 } 258 fmt.Printf("命令: echo $MY_VAR (MY_VAR=sandbox-value)\n") 259 fmt.Printf("stdout: %s", result.Stdout) 260 261 // WithCwd — 指定工作目录 262 result, err = sb.Commands().Run(ctx, "pwd", sandbox.WithCwd("/tmp")) 263 if err != nil { 264 log.Fatalf("执行命令失败: %v", err) 265 } 266 fmt.Printf("命令: pwd (cwd=/tmp)\nstdout: %s", result.Stdout) 267 268 // WithTimeout — 命令超时 269 result, err = sb.Commands().Run(ctx, "echo fast", sandbox.WithTimeout(5*time.Second)) 270 if err != nil { 271 log.Fatalf("执行命令失败: %v", err) 272 } 273 fmt.Printf("命令: echo fast (timeout=5s)\nstdout: %s", result.Stdout) 274 275 // WithOnStdout / WithOnStderr — 实时输出回调 276 fmt.Println("\n--- 实时输出回调 ---") 277 var stdoutChunks, stderrChunks int 278 result, err = sb.Commands().Run(ctx, "echo out-line && echo err-line >&2", 279 sandbox.WithOnStdout(func(data []byte) { stdoutChunks++ }), 280 sandbox.WithOnStderr(func(data []byte) { stderrChunks++ }), 281 ) 282 if err != nil { 283 log.Fatalf("执行命令失败: %v", err) 284 } 285 fmt.Printf("stdout 回调次数: %d, stderr 回调次数: %d\n", stdoutChunks, stderrChunks) 286 fmt.Printf("stdout: %sstderr: %s", result.Stdout, result.Stderr) 287 288 // Start / List / Kill — 后台命令管理 289 fmt.Println("\n--- 后台命令 (Start / List / Kill) ---") 290 handle, err := sb.Commands().Start(ctx, "sleep 30", sandbox.WithTag("bg-sleep")) 291 if err != nil { 292 log.Fatalf("Start 失败: %v", err) 293 } 294 // 等待 PID 分配 295 if _, err := handle.WaitPID(ctx); err != nil { 296 log.Fatalf("WaitPID 失败: %v", err) 297 } 298 fmt.Printf("后台命令已启动: PID=%d\n", handle.PID()) 299 300 // List — 列出运行中的进程 301 processes, err := sb.Commands().List(ctx) 302 if err != nil { 303 log.Fatalf("List 失败: %v", err) 304 } 305 fmt.Printf("运行中的进程 (%d 个):\n", len(processes)) 306 for _, p := range processes { 307 tag := "<none>" 308 if p.Tag != nil { 309 tag = *p.Tag 310 } 311 fmt.Printf(" PID=%d, cmd=%s, tag=%s\n", p.PID, p.Cmd, tag) 312 } 313 314 // Kill — 终止后台进程 315 if err := sb.Commands().Kill(ctx, handle.PID()); err != nil { 316 log.Fatalf("Kill 失败: %v", err) 317 } 318 fmt.Printf("进程 PID=%d 已终止\n", handle.PID()) 319 320 // 6. 下载/上传 URL 321 fmt.Println("\n--- 文件 URL ---") 322 downloadURL := sb.DownloadURL("/tmp/hello.txt") 323 fmt.Printf("下载 URL: %s\n", downloadURL) 324 325 uploadURL := sb.UploadURL("/tmp/upload.txt") 326 fmt.Printf("上传 URL: %s\n", uploadURL) 327 328 // 通过 Files().Write() / Files().Read() 进行文件上传下载 329 writeContent := []byte("uploaded via Files().Write()\n") 330 if _, err := sb.Files().Write(ctx, "/tmp/upload-test.txt", writeContent); err != nil { 331 log.Fatalf("Files().Write 失败: %v", err) 332 } 333 fmt.Println("Files().Write 成功: /tmp/upload-test.txt") 334 335 readContent, err := sb.Files().Read(ctx, "/tmp/upload-test.txt") 336 if err != nil { 337 log.Fatalf("Files().Read 失败: %v", err) 338 } 339 fmt.Printf("Files().Read 结果: %q\n", string(readContent)) 340 341 // 7. PTY 终端 342 fmt.Println("\n--- PTY 终端 ---") 343 344 // Create — 创建 PTY 会话 345 var ptyOutput []byte 346 ptyHandle, err := sb.Pty().Create(ctx, sandbox.PtySize{Cols: 80, Rows: 24}, 347 sandbox.WithOnStdout(func(data []byte) { 348 ptyOutput = append(ptyOutput, data...) 349 }), 350 ) 351 if err != nil { 352 log.Fatalf("Pty.Create 失败: %v", err) 353 } 354 if _, err := ptyHandle.WaitPID(ctx); err != nil { 355 log.Fatalf("WaitPID 失败: %v", err) 356 } 357 fmt.Printf("PTY 已创建: PID=%d\n", ptyHandle.PID()) 358 359 // SendInput — 向 PTY 发送输入 360 if err := sb.Pty().SendInput(ctx, ptyHandle.PID(), []byte("echo pty-hello\n")); err != nil { 361 log.Fatalf("Pty.SendInput 失败: %v", err) 362 } 363 time.Sleep(500 * time.Millisecond) 364 fmt.Printf("PTY 输出片段: %q\n", truncate(string(ptyOutput), 200)) 365 366 // Resize — 调整终端大小 367 if err := sb.Pty().Resize(ctx, ptyHandle.PID(), sandbox.PtySize{Cols: 120, Rows: 40}); err != nil { 368 log.Fatalf("Pty.Resize 失败: %v", err) 369 } 370 fmt.Println("PTY 已调整为 120x40") 371 372 // Kill — 终止 PTY 会话 373 if err := sb.Pty().Kill(ctx, ptyHandle.PID()); err != nil { 374 log.Fatalf("Pty.Kill 失败: %v", err) 375 } 376 fmt.Printf("PTY PID=%d 已终止\n", ptyHandle.PID()) 377 } 378 379 // boolPtr 返回 bool 值的指针。 380 func boolPtr(v bool) *bool { return &v } 381 382 // truncate 截断字符串到指定最大长度。 383 func truncate(s string, maxLen int) string { 384 if len(s) <= maxLen { 385 return s 386 } 387 return s[:maxLen] + "..." 388 }