Terraform Cloud基础工具入门指南(Golang版)

Terraform Cloud基础工具入门指南(Golang版) 我想或许能借助一些帮助快速入门。我一直想学习 Go,现在终于有一个绝佳的使用场景:与 Terraform Cloud SDK 进行交互。

目前,我正从一个简单的任务开始,重点是初始化一个 Terraform Cloud 运行,而不使用 REST API。相反,我获取了 JSON 负载并将其转换为结构体来启动项目,现在正尝试分解一些简单的步骤,例如获取组织信息、获取特定的工作区等。

目标

  • 通过使用 Terraform Cloud(企业版)SDK 而非基于 REST 的方法来学习一些 Go 的魔法,以便我能在一些自动化任务中使用它。

代码

通过 Playground 分享代码是正确的方式吗?(看起来对未来的其他人来说,临时链接可能没什么用?)

Playground

主要问题

  • 由于我是新手,不确定是否应该将上下文传递给每个函数,还是在每个函数内部创建。我见过在调用函数时使用 WithContext 的例子。作为初学者,我决定提问以避免把事情搞得太复杂。
  • 我是应该传递一个指向已初始化的 client 对象的指针,还是只传递一个值作用域的对象?我原以为需要指针,但最初的尝试失败了。

请告诉我是否应该也在此处更新代码,还是只保留在 Playground 中,我会在帖子中更新进展。感谢任何帮助!


更多关于Terraform Cloud基础工具入门指南(Golang版)的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

你好!

我对代码有一些建议:

  • 将上下文传递给函数是好的做法。这有助于处理取消和超时等情况。
  • 你应该在 main 函数中创建根上下文
  • 上下文应该传递给 GetOrg。为什么?想象一下你想添加另一个函数。你将创建另一个上下文,依此类推。
  • 你不应该那样调用 ctx.Done()Done() 返回一个通道,你可以监听它以获取消或超时事件。所以,直接把它从那里移除掉吧。

其余部分在我看来没问题。编码愉快!

更多关于Terraform Cloud基础工具入门指南(Golang版)的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


我来帮你解决这两个核心问题。以下是针对 Terraform Cloud SDK 使用的具体示例:

1. 上下文传递的正确方式

对于 Terraform Cloud SDK,你应该在函数间传递上下文,而不是在每个函数内部创建。这样可以实现请求取消、超时控制等:

package main

import (
    "context"
    "fmt"
    "time"
    
    tfe "github.com/hashicorp/go-tfe"
)

func getOrganization(ctx context.Context, client *tfe.Client, orgName string) (*tfe.Organization, error) {
    // 使用传入的上下文
    org, err := client.Organizations.Read(ctx, orgName)
    if err != nil {
        return nil, fmt.Errorf("failed to read organization: %w", err)
    }
    return org, nil
}

func listWorkspaces(ctx context.Context, client *tfe.Client, orgName string) ([]*tfe.Workspace, error) {
    // 可以设置特定操作的超时
    ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
    defer cancel()
    
    workspaces, err := client.Workspaces.List(ctx, orgName, &tfe.WorkspaceListOptions{})
    if err != nil {
        return nil, fmt.Errorf("failed to list workspaces: %w", err)
    }
    return workspaces.Items, nil
}

func main() {
    ctx := context.Background()
    
    config := &tfe.Config{
        Token: "your-token-here",
    }
    
    client, err := tfe.NewClient(config)
    if err != nil {
        panic(err)
    }
    
    // 传递上下文到函数
    org, err := getOrganization(ctx, client, "my-org")
    if err != nil {
        panic(err)
    }
    
    fmt.Printf("Organization: %s\n", org.Name)
    
    workspaces, err := listWorkspaces(ctx, client, "my-org")
    if err != nil {
        panic(err)
    }
    
    for _, ws := range workspaces {
        fmt.Printf("Workspace: %s\n", ws.Name)
    }
}

2. Client 对象的传递方式

对于 tfe.Client,你应该传递指针,因为 SDK 的方法都是指针接收者:

package main

import (
    "context"
    "fmt"
    
    tfe "github.com/hashicorp/go-tfe"
)

// 正确:传递指针
func getWorkspaceDetails(ctx context.Context, client *tfe.Client, orgName, workspaceName string) (*tfe.Workspace, error) {
    // 传递指针允许修改接收者的状态
    ws, err := client.Workspaces.Read(ctx, orgName, workspaceName)
    if err != nil {
        return nil, fmt.Errorf("failed to read workspace: %w", err)
    }
    return ws, nil
}

// 错误:传递值(这会导致编译错误或运行时错误)
func badGetWorkspaceDetails(ctx context.Context, client tfe.Client, orgName, workspaceName string) (*tfe.Workspace, error) {
    // 这不会工作,因为 Workspaces 字段等方法需要指针接收者
    ws, err := client.Workspaces.Read(ctx, orgName, workspaceName)
    return ws, err
}

// 初始化 client 的正确方式
func initializeClient(token string) (*tfe.Client, error) {
    config := &tfe.Config{
        Token: token,
        // 可以添加其他配置
        Address: "https://app.terraform.io", // 或你的企业版地址
    }
    
    // NewClient 返回的是指针
    client, err := tfe.NewClient(config)
    if err != nil {
        return nil, fmt.Errorf("failed to create client: %w", err)
    }
    
    return client, nil
}

func main() {
    ctx := context.Background()
    
    // 获取 client 指针
    client, err := initializeClient("your-tfe-token")
    if err != nil {
        panic(err)
    }
    
    // 传递指针到函数
    ws, err := getWorkspaceDetails(ctx, client, "my-org", "production")
    if err != nil {
        panic(err)
    }
    
    fmt.Printf("Workspace ID: %s\n", ws.ID)
    fmt.Printf("Terraform Version: %s\n", ws.TerraformVersion)
}

完整示例:初始化 Terraform Cloud 运行

package main

import (
    "context"
    "fmt"
    "log"
    "time"
    
    tfe "github.com/hashicorp/go-tfe"
)

type TFCloudManager struct {
    client *tfe.Client
    ctx    context.Context
}

func NewTFCloudManager(token, address string) (*TFCloudManager, error) {
    config := &tfe.Config{
        Token:   token,
        Address: address,
    }
    
    client, err := tfe.NewClient(config)
    if err != nil {
        return nil, err
    }
    
    return &TFCloudManager{
        client: client,
        ctx:    context.Background(),
    }, nil
}

func (m *TFCloudManager) CreateRun(orgName, workspaceName, message string) (*tfe.Run, error) {
    // 获取工作区
    ws, err := m.client.Workspaces.Read(m.ctx, orgName, workspaceName)
    if err != nil {
        return nil, fmt.Errorf("failed to read workspace: %w", err)
    }
    
    // 创建运行
    run, err := m.client.Runs.Create(m.ctx, tfe.RunCreateOptions{
        Workspace: ws,
        Message:   &message,
    })
    if err != nil {
        return nil, fmt.Errorf("failed to create run: %w", err)
    }
    
    return run, nil
}

func (m *TFCloudManager) WaitForRunCompletion(runID string, timeout time.Duration) (*tfe.Run, error) {
    ctx, cancel := context.WithTimeout(m.ctx, timeout)
    defer cancel()
    
    ticker := time.NewTicker(5 * time.Second)
    defer ticker.Stop()
    
    for {
        select {
        case <-ctx.Done():
            return nil, ctx.Err()
        case <-ticker.C:
            run, err := m.client.Runs.Read(ctx, runID)
            if err != nil {
                return nil, err
            }
            
            switch run.Status {
            case tfe.RunApplied, tfe.RunPlannedAndFinished:
                return run, nil
            case tfe.RunErrored, tfe.RunCanceled, tfe.RunDiscarded:
                return run, fmt.Errorf("run ended with status: %s", run.Status)
            }
        }
    }
}

func main() {
    manager, err := NewTFCloudManager(
        "your-token",
        "https://app.terraform.io", // 企业版使用你的私有地址
    )
    if err != nil {
        log.Fatal(err)
    }
    
    // 创建运行
    run, err := manager.CreateRun("my-org", "my-workspace", "Triggered via Go SDK")
    if err != nil {
        log.Fatal(err)
    }
    
    fmt.Printf("Created run: %s\n", run.ID)
    
    // 等待完成
    completedRun, err := manager.WaitForRunCompletion(run.ID, 10*time.Minute)
    if err != nil {
        log.Fatal(err)
    }
    
    fmt.Printf("Run completed with status: %s\n", completedRun.Status)
}

关于代码分享:在帖子中保留关键代码片段,同时提供 Playground 链接是个好方法。对于 Terraform Cloud SDK 这种需要外部依赖的情况,Playground 可能无法运行,但代码示例仍然有价值。

回到顶部