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  }