-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
tf.go
162 lines (148 loc) · 4.38 KB
/
tf.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
package cfft
import (
"context"
"encoding/json"
"errors"
"fmt"
"log/slog"
"strings"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/cloudfront"
"github.com/aws/aws-sdk-go-v2/service/cloudfront/types"
)
type TFCmd struct {
External bool `cmd:"" help:"output JSON for external data source"`
Publish *bool `cmd:"" help:"set publish flag" default:"false"`
ResourceName string `cmd:"" help:"resource name"`
}
type TFJSON struct {
Comment string `json:"//"`
Variable map[string]TFVar `json:"variable,omitempty"`
Resource TFCFF `json:"resource"`
}
type TFVar struct {
Type string `json:"type"`
Default string `json:"default"`
Description string `json:"description"`
}
const TFJSONComment = `This file is generated by cfft. DO NOT EDIT.`
type TFCFF struct {
AWSCloudFrontFunction map[string]TFOutout `json:"aws_cloudfront_function"`
}
type TFOutout struct {
Name string `json:"name"`
Code string `json:"code"`
Runtime types.FunctionRuntime `json:"runtime"`
Comment string `json:"comment"`
Publish *bool `json:"publish,omitempty"`
}
func (app *CFFT) RunTF(ctx context.Context, opt *TFCmd) error {
code, err := app.resolveTFFunctionCode(ctx)
if err != nil {
return err
}
localCode := string(code)
var rname string
if opt.ResourceName != "" {
rname = opt.ResourceName
} else {
rname = app.config.Name
}
out := TFOutout{
Name: rname,
Runtime: app.config.Runtime,
Comment: app.config.Comment,
}
enc := json.NewEncoder(app.stdout)
enc.SetIndent("", " ")
if opt.External {
// for external data source
out.Code = localCode
out.Publish = nil // external data source does not allows boolean value
return enc.Encode(out)
} else {
// output tf.json
out.Publish = opt.Publish // Publish flag is only for tf.json
resource := TFJSON{
Comment: TFJSONComment,
}
if strings.Contains(localCode, "${") {
// local code contains interpolation. use variable to avoid tf template evaluation error
varName := fmt.Sprintf("cfft_code_of_%s", rname)
resource.Variable = map[string]TFVar{
varName: {
Type: "string",
Default: localCode,
Description: "CloudFront Function code of " + app.config.Name,
},
}
out.Code = fmt.Sprintf("${var.%s}", varName)
} else {
// local code does not contain interpolation. use inline code
out.Code = localCode
}
resource.Resource = TFCFF{
AWSCloudFrontFunction: map[string]TFOutout{
rname: out,
},
}
return enc.Encode(&resource)
}
}
func (app *CFFT) resolveTFFunctionCode(ctx context.Context) ([]byte, error) {
localCode, err := app.config.FunctionCode(ctx)
if err != nil {
return nil, err
}
if isTesting(ctx) {
// for testing, return local code as is
return localCode, nil
}
developmentCode, err := app.getFunctionCode(ctx, types.FunctionStageDevelopment)
if err != nil {
return nil, err
}
liveCode, err := app.getFunctionCode(ctx, types.FunctionStageLive)
if err != nil {
return nil, err
}
needUpdate := false
if !isSameCode(localCode, developmentCode) {
// local != development
slog.Info("local code is different from development code")
needUpdate = true
} else if !isSameCode(developmentCode, liveCode) {
// local == development != live
slog.Info("development code is different from live code")
needUpdate = true
}
if needUpdate {
// should be updated with local code
// for detecting the change by tf, add a unique comment to the code
slog.Info("function code is not up-to-date")
comment := "generated by cfft tf at " + time.Now().Format(time.RFC3339)
return addCFFTHeader(localCode, comment), nil
} else {
// local == development == live
// no need to update, keep development code
slog.Debug("no need to update function code")
return developmentCode, nil
}
}
func (app *CFFT) getFunctionCode(ctx context.Context, stage types.FunctionStage) ([]byte, error) {
name := app.config.Name
res, err := app.cloudfront.GetFunction(ctx, &cloudfront.GetFunctionInput{
Name: aws.String(name),
Stage: stage,
})
if err != nil {
var notFound *types.NoSuchFunctionExists
if errors.As(err, ¬Found) {
slog.Info(f("function %s not found in %s stage", name, stage))
return nil, nil // not found is not an error
}
return nil, fmt.Errorf("failed to describe function, %w", err)
}
return res.FunctionCode, nil
}