-
Notifications
You must be signed in to change notification settings - Fork 894
chore: track terraform modules in telemetry #15450
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
0f713ed
82c8a3c
307a57e
2ae16fc
7f2f155
863235d
993f0ab
e8fbd40
5c542e5
9ec5ee1
119a686
3a5a025
3377d22
422b112
46b9b36
f3f4d5c
93ed136
301a153
212e7d5
e9b7c46
25cba6d
780730f
b47a018
03be52e
00b8131
4187fbf
7e82c3d
42e57ca
368fc25
73b22cf
7b9d70a
ea82313
8564e9a
d11fc82
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
-- name: InsertWorkspaceModule :one | ||
INSERT INTO | ||
workspace_modules (id, job_id, transition, source, version, key, created_at) | ||
VALUES | ||
($1, $2, $3, $4, $5, $6, $7) RETURNING *; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package terraform | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm curious about this. We extract all other resources from the template by executing Did this approach not work for modules? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I couldn't find either module versions or their sources inside the state. This was the first approach I tried, and I switched to reading files only when I couldn't make it work. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the output from a $ terraform graph
digraph G {
...
"module.jetbrains_gateway.coder_app.gateway" -> "coder_agent.main";
"module.jetbrains_gateway.coder_app.gateway" -> "module.jetbrains_gateway.data.coder_parameter.jetbrains_ide";
"module.jetbrains_gateway.coder_app.gateway" -> "module.jetbrains_gateway.data.coder_workspace.me";
"module.jetbrains_gateway.coder_app.gateway" -> "module.jetbrains_gateway.data.coder_workspace_owner.me";
"module.jetbrains_gateway.coder_app.gateway" -> "module.jetbrains_gateway.data.http.jetbrains_ide_versions";
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, the graph only displays the module names specified by the user in the template. It doesn’t show the source, like |
||
|
||
import ( | ||
"encoding/json" | ||
"os" | ||
"path/filepath" | ||
|
||
"golang.org/x/xerrors" | ||
|
||
"github.com/coder/coder/v2/provisionersdk/proto" | ||
) | ||
|
||
type module struct { | ||
Source string `json:"Source"` | ||
Version string `json:"Version"` | ||
Key string `json:"Key"` | ||
} | ||
|
||
type modulesFile struct { | ||
Modules []*module `json:"Modules"` | ||
} | ||
|
||
func getModulesFilePath(workdir string) string { | ||
return filepath.Join(workdir, ".terraform", "modules", "modules.json") | ||
} | ||
|
||
func parseModulesFile(filePath string) ([]*proto.Module, error) { | ||
modules := &modulesFile{} | ||
data, err := os.ReadFile(filePath) | ||
if err != nil { | ||
return nil, xerrors.Errorf("read modules file: %w", err) | ||
} | ||
if err := json.Unmarshal(data, modules); err != nil { | ||
return nil, xerrors.Errorf("unmarshal modules file: %w", err) | ||
} | ||
protoModules := make([]*proto.Module, len(modules.Modules)) | ||
for i, m := range modules.Modules { | ||
protoModules[i] = &proto.Module{Source: m.Source, Version: m.Version, Key: m.Key} | ||
} | ||
return protoModules, nil | ||
} | ||
|
||
// getModules returns the modules from the modules file if it exists. | ||
// It returns nil if the file does not exist. | ||
// Modules become available after terraform init. | ||
func getModules(workdir string) ([]*proto.Module, error) { | ||
filePath := getModulesFilePath(workdir) | ||
if _, err := os.Stat(filePath); os.IsNotExist(err) { | ||
return nil, nil | ||
} | ||
return parseModulesFile(filePath) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -142,6 +142,11 @@ func (s *server) Plan( | |
return provisionersdk.PlanErrorf("initialize terraform: %s", err) | ||
} | ||
|
||
modules, err := getModules(sess.WorkDirectory) | ||
if err != nil { | ||
return provisionersdk.PlanErrorf("get modules: %s", err) | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So, if we fail to get the list of modules for the build that we only need for telemetry purposes, we fail to build? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch, it'd probably be better to only log an error. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On second thought, after the discussion regarding using modules in places other than telemetry, it may make sense to return an error rather than just log. Otherwise any code using module info would have to take into account that it may be missing. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just so we're clear, this means that any failure to fetch the modules in the working directory will result in a failure of the whole plan stage. Right now, as far as I can tell, the only scenario that will trigger this is a malformed modules file. Are there any other possible scenarios I'm missing? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pair review (blocking): as this is currently only for telemetry, we should not fail to plan if anything goes wrong with extracting modules. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I don't think there are any other scenarios - barring disk read failures and such. In normal operation this should only happen if terraform changes how it stores modules on disk and we upgrade to that version. |
||
|
||
initTimings.ingest(createInitTimingsEvent(timingInitComplete)) | ||
|
||
s.logger.Debug(ctx, "ran initialization") | ||
|
@@ -167,6 +172,7 @@ func (s *server) Plan( | |
// Prepend init timings since they occur prior to plan timings. | ||
// Order is irrelevant; this is merely indicative. | ||
resp.Timings = append(initTimings.aggregate(), resp.Timings...) | ||
resp.Modules = modules | ||
return resp | ||
} | ||
|
||
|
Uh oh!
There was an error while loading. Please reload this page.