mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
Merge pull request #32781 from hashicorp/alisdair/rip-earlyconfig
initwd: Port from `earlyconfig` to `configs`
This commit is contained in:
commit
b088c67c60
1
go.mod
1
go.mod
@ -45,7 +45,6 @@ require (
|
||||
github.com/hashicorp/hcl v0.0.0-20170504190234-a4b07c25de5f
|
||||
github.com/hashicorp/hcl/v2 v2.16.0
|
||||
github.com/hashicorp/jsonapi v0.0.0-20210826224640-ee7dae0fb22d
|
||||
github.com/hashicorp/terraform-config-inspect v0.0.0-20210209133302-4fd17a0faac2
|
||||
github.com/hashicorp/terraform-registry-address v0.0.0-20220623143253-7d51757b572c
|
||||
github.com/hashicorp/terraform-svchost v0.1.0
|
||||
github.com/jmespath/go-jmespath v0.4.0
|
||||
|
16
go.sum
16
go.sum
@ -250,8 +250,6 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM=
|
||||
github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
|
||||
github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
|
||||
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
|
||||
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1501 h1:Ij3S0pNUMgHlhx3Ew8g9RNrt59EKhHYdMODGtFXJfSc=
|
||||
@ -267,7 +265,6 @@ github.com/antchfx/xpath v1.1.10/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByN
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/apparentlymart/go-cidr v1.1.0 h1:2mAhrMoF+nhXqxTzSZMUzDHkLjmIHC+Zzn4tdgBZjnU=
|
||||
github.com/apparentlymart/go-cidr v1.1.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc=
|
||||
github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM=
|
||||
github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0 h1:MzVXffFUye+ZcSR6opIgz9Co7WcDx6ZcY+RjfFHoA0I=
|
||||
github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM=
|
||||
github.com/apparentlymart/go-shquot v0.0.1 h1:MGV8lwxF4zw75lN7e0MGs7o6AFYn7L6AZaExUpLh0Mo=
|
||||
@ -562,7 +559,6 @@ github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/hcl v0.0.0-20170504190234-a4b07c25de5f h1:UdxlrJz4JOnY8W+DbLISwf2B8WXEolNRA8BGCwI9jws=
|
||||
github.com/hashicorp/hcl v0.0.0-20170504190234-a4b07c25de5f/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w=
|
||||
github.com/hashicorp/hcl/v2 v2.0.0/go.mod h1:oVVDG71tEinNGYCxinCYadcmKU9bglqW9pV3txagJ90=
|
||||
github.com/hashicorp/hcl/v2 v2.16.0 h1:MPq1q615H+9wBAdE3EbwEd6imSohElrIguuasbQruB0=
|
||||
github.com/hashicorp/hcl/v2 v2.16.0/go.mod h1:JRmR89jycNkrrqnMmvPDMd56n1rQJ2Q6KocSLCMCXng=
|
||||
github.com/hashicorp/jsonapi v0.0.0-20210826224640-ee7dae0fb22d h1:9ARUJJ1VVynB176G1HCwleORqCaXm/Vx0uUi0dL26I0=
|
||||
@ -573,8 +569,6 @@ github.com/hashicorp/memberlist v0.2.2 h1:5+RffWKwqJ71YPu9mWsF7ZOscZmwfasdA8kbdC
|
||||
github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
|
||||
github.com/hashicorp/serf v0.9.5 h1:EBWvyu9tcRszt3Bxp3KNssBMP1KuHWyO51lz9+786iM=
|
||||
github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
|
||||
github.com/hashicorp/terraform-config-inspect v0.0.0-20210209133302-4fd17a0faac2 h1:l+bLFvHjqtgNQwWxwrFX9PemGAAO2P1AGZM7zlMNvCs=
|
||||
github.com/hashicorp/terraform-config-inspect v0.0.0-20210209133302-4fd17a0faac2/go.mod h1:Z0Nnk4+3Cy89smEbrq+sl1bxc9198gIP4I7wcQF6Kqs=
|
||||
github.com/hashicorp/terraform-registry-address v0.0.0-20220623143253-7d51757b572c h1:D8aRO6+mTqHfLsK/BC3j5OAoogv1WLRWzY1AaTo3rBg=
|
||||
github.com/hashicorp/terraform-registry-address v0.0.0-20220623143253-7d51757b572c/go.mod h1:Wn3Na71knbXc1G8Lh+yu/dQWWJeFQEpDeJMtWMtlmNI=
|
||||
github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734/go.mod h1:kNDNcF7sN4DocDLBkQYz73HGKwN1ANB1blq4lIYLYvg=
|
||||
@ -622,9 +616,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
|
||||
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/lib/pq v1.10.3 h1:v9QZf2Sn6AmjXtQeFpdoq/eaNtYP6IN+7lcrygsIAtg=
|
||||
github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
@ -676,8 +669,6 @@ github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.
|
||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU=
|
||||
github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=
|
||||
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
|
||||
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
|
||||
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
|
||||
github.com/mitchellh/gox v1.0.1 h1:x0jD3dcHk9a9xPSDN6YEL4xL6Qz0dvNYm8yZqui5chI=
|
||||
@ -744,7 +735,6 @@ github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
|
||||
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
@ -757,8 +747,6 @@ github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTd
|
||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
|
||||
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
|
||||
github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
|
||||
@ -825,7 +813,6 @@ go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqe
|
||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190222235706-ffb98f73852f/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
|
||||
@ -993,7 +980,6 @@ golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -14,7 +14,7 @@ func TestChecksHappyPath(t *testing.T) {
|
||||
const fixtureDir = "testdata/happypath"
|
||||
loader, close := configload.NewLoaderForTests(t)
|
||||
defer close()
|
||||
inst := initwd.NewModuleInstaller(loader.ModulesDir(), nil)
|
||||
inst := initwd.NewModuleInstaller(loader.ModulesDir(), loader, nil)
|
||||
_, instDiags := inst.InstallModules(context.Background(), fixtureDir, true, initwd.ModuleInstallHooksImpl{})
|
||||
if instDiags.HasErrors() {
|
||||
t.Fatal(instDiags.Err())
|
||||
|
@ -154,7 +154,7 @@ func testModuleWithSnapshot(t *testing.T, name string) (*configs.Config, *config
|
||||
// Test modules usually do not refer to remote sources, and for local
|
||||
// sources only this ultimately just records all of the module paths
|
||||
// in a JSON file so that we can load them below.
|
||||
inst := initwd.NewModuleInstaller(loader.ModulesDir(), registry.NewClient(nil, nil))
|
||||
inst := initwd.NewModuleInstaller(loader.ModulesDir(), loader, registry.NewClient(nil, nil))
|
||||
_, instDiags := inst.InstallModules(context.Background(), dir, true, initwd.ModuleInstallHooksImpl{})
|
||||
if instDiags.HasErrors() {
|
||||
t.Fatal(instDiags.Err())
|
||||
|
@ -152,7 +152,13 @@ func (m *Meta) installModules(rootDir string, upgrade bool, hooks initwd.ModuleI
|
||||
return true, diags
|
||||
}
|
||||
|
||||
inst := m.moduleInstaller()
|
||||
loader, err := m.initConfigLoader()
|
||||
if err != nil {
|
||||
diags = diags.Append(err)
|
||||
return true, diags
|
||||
}
|
||||
|
||||
inst := initwd.NewModuleInstaller(m.modulesDir(), loader, m.registryClient())
|
||||
|
||||
// Installation can be aborted by interruption signals
|
||||
ctx, done := m.InterruptibleContext()
|
||||
@ -184,8 +190,14 @@ func (m *Meta) initDirFromModule(targetDir string, addr string, hooks initwd.Mod
|
||||
ctx, done := m.InterruptibleContext()
|
||||
defer done()
|
||||
|
||||
loader, err := m.initConfigLoader()
|
||||
if err != nil {
|
||||
diags = diags.Append(err)
|
||||
return true, diags
|
||||
}
|
||||
|
||||
targetDir = m.normalizePath(targetDir)
|
||||
moreDiags := initwd.DirFromModule(ctx, targetDir, m.modulesDir(), addr, m.registryClient(), hooks)
|
||||
moreDiags := initwd.DirFromModule(ctx, loader, targetDir, m.modulesDir(), addr, m.registryClient(), hooks)
|
||||
diags = diags.Append(moreDiags)
|
||||
if ctx.Err() == context.Canceled {
|
||||
m.showDiagnostics(diags)
|
||||
@ -316,13 +328,6 @@ func (m *Meta) initConfigLoader() (*configload.Loader, error) {
|
||||
return m.configLoader, nil
|
||||
}
|
||||
|
||||
// moduleInstaller instantiates and returns a module installer for use by
|
||||
// "terraform init" (directly or indirectly).
|
||||
func (m *Meta) moduleInstaller() *initwd.ModuleInstaller {
|
||||
reg := m.registryClient()
|
||||
return initwd.NewModuleInstaller(m.modulesDir(), reg)
|
||||
}
|
||||
|
||||
// registryClient instantiates and returns a new Terraform Registry client.
|
||||
func (m *Meta) registryClient() *registry.Client {
|
||||
return registry.NewClient(m.Services, nil)
|
||||
|
@ -248,7 +248,12 @@ func (c *TestCommand) prepareSuiteDir(ctx context.Context, suiteName string) (te
|
||||
suiteDirs.ModulesDir = filepath.Join(configDir, ".terraform", "modules")
|
||||
os.MkdirAll(suiteDirs.ModulesDir, 0755) // if this fails then we'll ignore it and let InstallModules below fail instead
|
||||
reg := c.registryClient()
|
||||
moduleInst := initwd.NewModuleInstaller(suiteDirs.ModulesDir, reg)
|
||||
loader, err := c.initConfigLoader()
|
||||
if err != nil {
|
||||
diags = diags.Append(err)
|
||||
return suiteDirs, diags
|
||||
}
|
||||
moduleInst := initwd.NewModuleInstaller(suiteDirs.ModulesDir, loader, reg)
|
||||
_, moreDiags := moduleInst.InstallModules(ctx, configDir, true, nil)
|
||||
diags = diags.Append(moreDiags)
|
||||
if diags.HasErrors() {
|
||||
@ -260,7 +265,7 @@ func (c *TestCommand) prepareSuiteDir(ctx context.Context, suiteName string) (te
|
||||
// with a separate config loader because the Meta.configLoader instance
|
||||
// is intended for interacting with the current working directory, not
|
||||
// with the test suite subdirectories.
|
||||
loader, err := configload.NewLoader(&configload.Config{
|
||||
loader, err = configload.NewLoader(&configload.Config{
|
||||
ModulesDir: suiteDirs.ModulesDir,
|
||||
Services: c.Services,
|
||||
})
|
||||
|
@ -555,3 +555,16 @@ func (c *Config) ProviderForConfigAddr(addr addrs.LocalProviderConfig) addrs.Pro
|
||||
}
|
||||
return c.ResolveAbsProviderAddr(addr, addrs.RootModule).Provider
|
||||
}
|
||||
|
||||
func (c *Config) CheckCoreVersionRequirements() hcl.Diagnostics {
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
diags = diags.Extend(c.Module.CheckCoreVersionRequirements(c.Path, c.SourceAddr))
|
||||
|
||||
for _, c := range c.Children {
|
||||
childDiags := c.CheckCoreVersionRequirements()
|
||||
diags = diags.Extend(childDiags)
|
||||
}
|
||||
|
||||
return diags
|
||||
}
|
||||
|
@ -7,6 +7,8 @@ import (
|
||||
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/experiments"
|
||||
|
||||
tfversion "github.com/hashicorp/terraform/version"
|
||||
)
|
||||
|
||||
// Module is a container for a set of configuration constructs that are
|
||||
@ -589,3 +591,59 @@ func (m *Module) ImpliedProviderForUnqualifiedType(pType string) addrs.Provider
|
||||
}
|
||||
return addrs.ImpliedProviderForUnqualifiedType(pType)
|
||||
}
|
||||
|
||||
func (m *Module) CheckCoreVersionRequirements(path addrs.Module, sourceAddr addrs.ModuleSource) hcl.Diagnostics {
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
for _, constraint := range m.CoreVersionConstraints {
|
||||
// Before checking if the constraints are met, check that we are not using any prerelease fields as these
|
||||
// are not currently supported.
|
||||
var prereleaseDiags hcl.Diagnostics
|
||||
for _, required := range constraint.Required {
|
||||
if required.Prerelease() {
|
||||
prereleaseDiags = prereleaseDiags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid required_version constraint",
|
||||
Detail: fmt.Sprintf(
|
||||
"Prerelease version constraints are not supported: %s. Remove the prerelease information from the constraint. Prerelease versions of terraform will match constraints using their version core only.",
|
||||
required.String()),
|
||||
Subject: constraint.DeclRange.Ptr(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if len(prereleaseDiags) > 0 {
|
||||
// There were some prerelease fields in the constraints. Don't check the constraints as they will
|
||||
// fail, and populate the diagnostics for these constraints with the prerelease diagnostics.
|
||||
diags = diags.Extend(prereleaseDiags)
|
||||
continue
|
||||
}
|
||||
|
||||
if !constraint.Required.Check(tfversion.SemVer) {
|
||||
switch {
|
||||
case len(path) == 0:
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unsupported Terraform Core version",
|
||||
Detail: fmt.Sprintf(
|
||||
"This configuration does not support Terraform version %s. To proceed, either choose another supported Terraform version or update this version constraint. Version constraints are normally set for good reason, so updating the constraint may lead to other errors or unexpected behavior.",
|
||||
tfversion.String(),
|
||||
),
|
||||
Subject: constraint.DeclRange.Ptr(),
|
||||
})
|
||||
default:
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unsupported Terraform Core version",
|
||||
Detail: fmt.Sprintf(
|
||||
"Module %s (from %s) does not support Terraform version %s. To proceed, either choose another supported Terraform version or update this version constraint. Version constraints are normally set for good reason, so updating the constraint may lead to other errors or unexpected behavior.",
|
||||
path, sourceAddr, tfversion.String(),
|
||||
),
|
||||
Subject: constraint.DeclRange.Ptr(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return diags
|
||||
}
|
||||
|
@ -1,210 +0,0 @@
|
||||
package earlyconfig
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
version "github.com/hashicorp/go-version"
|
||||
"github.com/hashicorp/terraform-config-inspect/tfconfig"
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/getproviders"
|
||||
"github.com/hashicorp/terraform/internal/moduledeps"
|
||||
"github.com/hashicorp/terraform/internal/plugin/discovery"
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
)
|
||||
|
||||
// A Config is a node in the tree of modules within a configuration.
|
||||
//
|
||||
// The module tree is constructed by following ModuleCall instances recursively
|
||||
// through the root module transitively into descendent modules.
|
||||
type Config struct {
|
||||
// RootModule points to the Config for the root module within the same
|
||||
// module tree as this module. If this module _is_ the root module then
|
||||
// this is self-referential.
|
||||
Root *Config
|
||||
|
||||
// ParentModule points to the Config for the module that directly calls
|
||||
// this module. If this is the root module then this field is nil.
|
||||
Parent *Config
|
||||
|
||||
// Path is a sequence of module logical names that traverse from the root
|
||||
// module to this config. Path is empty for the root module.
|
||||
//
|
||||
// This should only be used to display paths to the end-user in rare cases
|
||||
// where we are talking about the static module tree, before module calls
|
||||
// have been resolved. In most cases, an addrs.ModuleInstance describing
|
||||
// a node in the dynamic module tree is better, since it will then include
|
||||
// any keys resulting from evaluating "count" and "for_each" arguments.
|
||||
Path addrs.Module
|
||||
|
||||
// ChildModules points to the Config for each of the direct child modules
|
||||
// called from this module. The keys in this map match the keys in
|
||||
// Module.ModuleCalls.
|
||||
Children map[string]*Config
|
||||
|
||||
// Module points to the object describing the configuration for the
|
||||
// various elements (variables, resources, etc) defined by this module.
|
||||
Module *tfconfig.Module
|
||||
|
||||
// CallPos is the source position for the header of the module block that
|
||||
// requested this module.
|
||||
//
|
||||
// This field is meaningless for the root module, where its contents are undefined.
|
||||
CallPos tfconfig.SourcePos
|
||||
|
||||
// SourceAddr is the source address that the referenced module was requested
|
||||
// from, as specified in configuration.
|
||||
//
|
||||
// This field is meaningless for the root module, where its contents are undefined.
|
||||
SourceAddr addrs.ModuleSource
|
||||
|
||||
// Version is the specific version that was selected for this module,
|
||||
// based on version constraints given in configuration.
|
||||
//
|
||||
// This field is nil if the module was loaded from a non-registry source,
|
||||
// since versions are not supported for other sources.
|
||||
//
|
||||
// This field is meaningless for the root module, where it will always
|
||||
// be nil.
|
||||
Version *version.Version
|
||||
}
|
||||
|
||||
// ProviderRequirements searches the full tree of modules under the receiver
|
||||
// for both explicit and implicit dependencies on providers.
|
||||
//
|
||||
// The result is a full manifest of all of the providers that must be available
|
||||
// in order to work with the receiving configuration.
|
||||
//
|
||||
// If the returned diagnostics includes errors then the resulting Requirements
|
||||
// may be incomplete.
|
||||
func (c *Config) ProviderRequirements() (getproviders.Requirements, tfdiags.Diagnostics) {
|
||||
reqs := make(getproviders.Requirements)
|
||||
diags := c.addProviderRequirements(reqs)
|
||||
return reqs, diags
|
||||
}
|
||||
|
||||
// addProviderRequirements is the main part of the ProviderRequirements
|
||||
// implementation, gradually mutating a shared requirements object to
|
||||
// eventually return.
|
||||
func (c *Config) addProviderRequirements(reqs getproviders.Requirements) tfdiags.Diagnostics {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
// First we'll deal with the requirements directly in _our_ module...
|
||||
for localName, providerReqs := range c.Module.RequiredProviders {
|
||||
var fqn addrs.Provider
|
||||
if source := providerReqs.Source; source != "" {
|
||||
addr, moreDiags := addrs.ParseProviderSourceString(source)
|
||||
if moreDiags.HasErrors() {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Invalid provider source address",
|
||||
fmt.Sprintf("Invalid source %q for provider %q in %s", source, localName, c.Path),
|
||||
))
|
||||
continue
|
||||
}
|
||||
fqn = addr
|
||||
}
|
||||
if fqn.IsZero() {
|
||||
fqn = addrs.ImpliedProviderForUnqualifiedType(localName)
|
||||
}
|
||||
if _, ok := reqs[fqn]; !ok {
|
||||
// We'll at least have an unconstrained dependency then, but might
|
||||
// add to this in the loop below.
|
||||
reqs[fqn] = nil
|
||||
}
|
||||
for _, constraintsStr := range providerReqs.VersionConstraints {
|
||||
if constraintsStr != "" {
|
||||
constraints, err := getproviders.ParseVersionConstraints(constraintsStr)
|
||||
if err != nil {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Invalid provider version constraint",
|
||||
fmt.Sprintf("Provider %q in %s has invalid version constraint %q: %s.", localName, c.Path, constraintsStr, err),
|
||||
))
|
||||
continue
|
||||
}
|
||||
reqs[fqn] = append(reqs[fqn], constraints...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ...and now we'll recursively visit all of the child modules to merge
|
||||
// in their requirements too.
|
||||
for _, childConfig := range c.Children {
|
||||
moreDiags := childConfig.addProviderRequirements(reqs)
|
||||
diags = diags.Append(moreDiags)
|
||||
}
|
||||
|
||||
return diags
|
||||
}
|
||||
|
||||
// ProviderDependencies is a deprecated variant of ProviderRequirements which
|
||||
// uses the moduledeps models for representation. This is preserved to allow
|
||||
// a gradual transition over to ProviderRequirements, but note that its
|
||||
// support for fully-qualified provider addresses has some idiosyncracies.
|
||||
func (c *Config) ProviderDependencies() (*moduledeps.Module, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
var name string
|
||||
if len(c.Path) > 0 {
|
||||
name = c.Path[len(c.Path)-1]
|
||||
}
|
||||
|
||||
ret := &moduledeps.Module{
|
||||
Name: name,
|
||||
}
|
||||
|
||||
providers := make(moduledeps.Providers)
|
||||
for name, reqs := range c.Module.RequiredProviders {
|
||||
var fqn addrs.Provider
|
||||
if source := reqs.Source; source != "" {
|
||||
addr, parseDiags := addrs.ParseProviderSourceString(source)
|
||||
if parseDiags.HasErrors() {
|
||||
diags = diags.Append(wrapDiagnostic(tfconfig.Diagnostic{
|
||||
Severity: tfconfig.DiagError,
|
||||
Summary: "Invalid provider source",
|
||||
Detail: fmt.Sprintf("Invalid source %q for provider", name),
|
||||
}))
|
||||
continue
|
||||
}
|
||||
fqn = addr
|
||||
}
|
||||
if fqn.IsZero() {
|
||||
fqn = addrs.NewDefaultProvider(name)
|
||||
}
|
||||
var constraints version.Constraints
|
||||
for _, reqStr := range reqs.VersionConstraints {
|
||||
if reqStr != "" {
|
||||
constraint, err := version.NewConstraint(reqStr)
|
||||
if err != nil {
|
||||
diags = diags.Append(wrapDiagnostic(tfconfig.Diagnostic{
|
||||
Severity: tfconfig.DiagError,
|
||||
Summary: "Invalid provider version constraint",
|
||||
Detail: fmt.Sprintf("Invalid version constraint %q for provider %s.", reqStr, fqn.String()),
|
||||
}))
|
||||
continue
|
||||
}
|
||||
constraints = append(constraints, constraint...)
|
||||
}
|
||||
}
|
||||
providers[fqn] = moduledeps.ProviderDependency{
|
||||
Constraints: discovery.NewConstraints(constraints),
|
||||
Reason: moduledeps.ProviderDependencyExplicit,
|
||||
}
|
||||
}
|
||||
ret.Providers = providers
|
||||
|
||||
childNames := make([]string, 0, len(c.Children))
|
||||
for name := range c.Children {
|
||||
childNames = append(childNames, name)
|
||||
}
|
||||
sort.Strings(childNames)
|
||||
|
||||
for _, name := range childNames {
|
||||
child, childDiags := c.Children[name].ProviderDependencies()
|
||||
ret.Children = append(ret.Children, child)
|
||||
diags = diags.Append(childDiags)
|
||||
}
|
||||
|
||||
return ret, diags
|
||||
}
|
@ -1,173 +0,0 @@
|
||||
package earlyconfig
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
version "github.com/hashicorp/go-version"
|
||||
"github.com/hashicorp/terraform-config-inspect/tfconfig"
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
)
|
||||
|
||||
// BuildConfig constructs a Config from a root module by loading all of its
|
||||
// descendent modules via the given ModuleWalker.
|
||||
func BuildConfig(root *tfconfig.Module, walker ModuleWalker) (*Config, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
cfg := &Config{
|
||||
Module: root,
|
||||
}
|
||||
cfg.Root = cfg // Root module is self-referential.
|
||||
cfg.Children, diags = buildChildModules(cfg, walker)
|
||||
return cfg, diags
|
||||
}
|
||||
|
||||
func buildChildModules(parent *Config, walker ModuleWalker) (map[string]*Config, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
ret := map[string]*Config{}
|
||||
calls := parent.Module.ModuleCalls
|
||||
|
||||
// We'll sort the calls by their local names so that they'll appear in a
|
||||
// predictable order in any logging that's produced during the walk.
|
||||
callNames := make([]string, 0, len(calls))
|
||||
for k := range calls {
|
||||
callNames = append(callNames, k)
|
||||
}
|
||||
sort.Strings(callNames)
|
||||
|
||||
for _, callName := range callNames {
|
||||
call := calls[callName]
|
||||
path := make([]string, len(parent.Path)+1)
|
||||
copy(path, parent.Path)
|
||||
path[len(path)-1] = call.Name
|
||||
|
||||
var vc version.Constraints
|
||||
haveVersionArg := false
|
||||
if strings.TrimSpace(call.Version) != "" {
|
||||
haveVersionArg = true
|
||||
|
||||
var err error
|
||||
vc, err = version.NewConstraint(call.Version)
|
||||
if err != nil {
|
||||
diags = diags.Append(wrapDiagnostic(tfconfig.Diagnostic{
|
||||
Severity: tfconfig.DiagError,
|
||||
Summary: "Invalid version constraint",
|
||||
Detail: fmt.Sprintf("Module %q (declared at %s line %d) has invalid version constraint %q: %s.", callName, call.Pos.Filename, call.Pos.Line, call.Version, err),
|
||||
}))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
var sourceAddr addrs.ModuleSource
|
||||
var err error
|
||||
if haveVersionArg {
|
||||
sourceAddr, err = addrs.ParseModuleSourceRegistry(call.Source)
|
||||
} else {
|
||||
sourceAddr, err = addrs.ParseModuleSource(call.Source)
|
||||
}
|
||||
if err != nil {
|
||||
if haveVersionArg {
|
||||
diags = diags.Append(wrapDiagnostic(tfconfig.Diagnostic{
|
||||
Severity: tfconfig.DiagError,
|
||||
Summary: "Invalid registry module source address",
|
||||
Detail: fmt.Sprintf("Module %q (declared at %s line %d) has invalid source address %q: %s.\n\nTerraform assumed that you intended a module registry source address because you also set the argument \"version\", which applies only to registry modules.", callName, call.Pos.Filename, call.Pos.Line, call.Source, err),
|
||||
}))
|
||||
} else {
|
||||
diags = diags.Append(wrapDiagnostic(tfconfig.Diagnostic{
|
||||
Severity: tfconfig.DiagError,
|
||||
Summary: "Invalid module source address",
|
||||
Detail: fmt.Sprintf("Module %q (declared at %s line %d) has invalid source address %q: %s.", callName, call.Pos.Filename, call.Pos.Line, call.Source, err),
|
||||
}))
|
||||
}
|
||||
// If we didn't have a valid source address then we can't continue
|
||||
// down the module tree with this one.
|
||||
continue
|
||||
}
|
||||
|
||||
req := ModuleRequest{
|
||||
Name: call.Name,
|
||||
Path: path,
|
||||
SourceAddr: sourceAddr,
|
||||
VersionConstraints: vc,
|
||||
Parent: parent,
|
||||
CallPos: call.Pos,
|
||||
}
|
||||
|
||||
mod, ver, modDiags := walker.LoadModule(&req)
|
||||
diags = append(diags, modDiags...)
|
||||
if mod == nil {
|
||||
// nil can be returned if the source address was invalid and so
|
||||
// nothing could be loaded whatsoever. LoadModule should've
|
||||
// returned at least one error diagnostic in that case.
|
||||
continue
|
||||
}
|
||||
|
||||
child := &Config{
|
||||
Parent: parent,
|
||||
Root: parent.Root,
|
||||
Path: path,
|
||||
Module: mod,
|
||||
CallPos: call.Pos,
|
||||
SourceAddr: sourceAddr,
|
||||
Version: ver,
|
||||
}
|
||||
|
||||
child.Children, modDiags = buildChildModules(child, walker)
|
||||
diags = diags.Append(modDiags)
|
||||
|
||||
ret[call.Name] = child
|
||||
}
|
||||
|
||||
return ret, diags
|
||||
}
|
||||
|
||||
// ModuleRequest is used as part of the ModuleWalker interface used with
|
||||
// function BuildConfig.
|
||||
type ModuleRequest struct {
|
||||
// Name is the "logical name" of the module call within configuration.
|
||||
// This is provided in case the name is used as part of a storage key
|
||||
// for the module, but implementations must otherwise treat it as an
|
||||
// opaque string. It is guaranteed to have already been validated as an
|
||||
// HCL identifier and UTF-8 encoded.
|
||||
Name string
|
||||
|
||||
// Path is a list of logical names that traverse from the root module to
|
||||
// this module. This can be used, for example, to form a lookup key for
|
||||
// each distinct module call in a configuration, allowing for multiple
|
||||
// calls with the same name at different points in the tree.
|
||||
Path addrs.Module
|
||||
|
||||
// SourceAddr is the source address string provided by the user in
|
||||
// configuration.
|
||||
SourceAddr addrs.ModuleSource
|
||||
|
||||
// VersionConstraint is the version constraint applied to the module in
|
||||
// configuration.
|
||||
VersionConstraints version.Constraints
|
||||
|
||||
// Parent is the partially-constructed module tree node that the loaded
|
||||
// module will be added to. Callers may refer to any field of this
|
||||
// structure except Children, which is still under construction when
|
||||
// ModuleRequest objects are created and thus has undefined content.
|
||||
// The main reason this is provided is so that full module paths can
|
||||
// be constructed for uniqueness.
|
||||
Parent *Config
|
||||
|
||||
// CallRange is the source position for the header of the "module" block
|
||||
// in configuration that prompted this request.
|
||||
CallPos tfconfig.SourcePos
|
||||
}
|
||||
|
||||
// ModuleWalker is an interface used with BuildConfig.
|
||||
type ModuleWalker interface {
|
||||
LoadModule(req *ModuleRequest) (*tfconfig.Module, *version.Version, tfdiags.Diagnostics)
|
||||
}
|
||||
|
||||
// ModuleWalkerFunc is an implementation of ModuleWalker that directly wraps
|
||||
// a callback function, for more convenient use of that interface.
|
||||
type ModuleWalkerFunc func(req *ModuleRequest) (*tfconfig.Module, *version.Version, tfdiags.Diagnostics)
|
||||
|
||||
func (f ModuleWalkerFunc) LoadModule(req *ModuleRequest) (*tfconfig.Module, *version.Version, tfdiags.Diagnostics) {
|
||||
return f(req)
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
package earlyconfig
|
||||
|
||||
import (
|
||||
"log"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
version "github.com/hashicorp/go-version"
|
||||
"github.com/hashicorp/terraform-config-inspect/tfconfig"
|
||||
svchost "github.com/hashicorp/terraform-svchost"
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/getproviders"
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
)
|
||||
|
||||
func TestConfigProviderRequirements(t *testing.T) {
|
||||
cfg := testConfig(t, "testdata/provider-reqs")
|
||||
|
||||
impliedProvider := addrs.NewProvider(
|
||||
addrs.DefaultProviderRegistryHost,
|
||||
"hashicorp", "implied",
|
||||
)
|
||||
nullProvider := addrs.NewProvider(
|
||||
addrs.DefaultProviderRegistryHost,
|
||||
"hashicorp", "null",
|
||||
)
|
||||
randomProvider := addrs.NewProvider(
|
||||
addrs.DefaultProviderRegistryHost,
|
||||
"hashicorp", "random",
|
||||
)
|
||||
tlsProvider := addrs.NewProvider(
|
||||
addrs.DefaultProviderRegistryHost,
|
||||
"hashicorp", "tls",
|
||||
)
|
||||
happycloudProvider := addrs.NewProvider(
|
||||
svchost.Hostname("tf.example.com"),
|
||||
"awesomecorp", "happycloud",
|
||||
)
|
||||
|
||||
got, diags := cfg.ProviderRequirements()
|
||||
if diags.HasErrors() {
|
||||
t.Fatalf("unexpected diagnostics: %s", diags.Err().Error())
|
||||
}
|
||||
want := getproviders.Requirements{
|
||||
// the nullProvider constraints from the two modules are merged
|
||||
nullProvider: getproviders.MustParseVersionConstraints("~> 2.0.0, 2.0.1"),
|
||||
randomProvider: getproviders.MustParseVersionConstraints("~> 1.2.0"),
|
||||
tlsProvider: getproviders.MustParseVersionConstraints("~> 3.0"),
|
||||
impliedProvider: nil,
|
||||
happycloudProvider: nil,
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Errorf("wrong result\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
func testConfig(t *testing.T, baseDir string) *Config {
|
||||
rootMod, diags := LoadModule(baseDir)
|
||||
if diags.HasErrors() {
|
||||
t.Fatalf("unexpected diagnostics: %s", diags.Err().Error())
|
||||
}
|
||||
|
||||
cfg, diags := BuildConfig(rootMod, ModuleWalkerFunc(testModuleWalkerFunc))
|
||||
if diags.HasErrors() {
|
||||
t.Fatalf("unexpected diagnostics: %s", diags.Err().Error())
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
// testModuleWalkerFunc is a simple implementation of ModuleWalkerFunc that
|
||||
// only understands how to resolve relative filesystem paths, using source
|
||||
// location information from the call.
|
||||
func testModuleWalkerFunc(req *ModuleRequest) (*tfconfig.Module, *version.Version, tfdiags.Diagnostics) {
|
||||
callFilename := req.CallPos.Filename
|
||||
sourcePath := req.SourceAddr.String()
|
||||
finalPath := filepath.Join(filepath.Dir(callFilename), sourcePath)
|
||||
log.Printf("[TRACE] %s in %s -> %s", sourcePath, callFilename, finalPath)
|
||||
|
||||
newMod, diags := LoadModule(finalPath)
|
||||
return newMod, version.Must(version.NewVersion("0.0.0")), diags
|
||||
}
|
@ -1,82 +0,0 @@
|
||||
package earlyconfig
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/terraform-config-inspect/tfconfig"
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
)
|
||||
|
||||
func wrapDiagnostics(diags tfconfig.Diagnostics) tfdiags.Diagnostics {
|
||||
ret := make(tfdiags.Diagnostics, len(diags))
|
||||
for i, diag := range diags {
|
||||
ret[i] = wrapDiagnostic(diag)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func wrapDiagnostic(diag tfconfig.Diagnostic) tfdiags.Diagnostic {
|
||||
return wrappedDiagnostic{
|
||||
d: diag,
|
||||
}
|
||||
}
|
||||
|
||||
type wrappedDiagnostic struct {
|
||||
d tfconfig.Diagnostic
|
||||
}
|
||||
|
||||
func (d wrappedDiagnostic) Severity() tfdiags.Severity {
|
||||
switch d.d.Severity {
|
||||
case tfconfig.DiagError:
|
||||
return tfdiags.Error
|
||||
case tfconfig.DiagWarning:
|
||||
return tfdiags.Warning
|
||||
default:
|
||||
// Should never happen since there are no other severities
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func (d wrappedDiagnostic) Description() tfdiags.Description {
|
||||
// Since the inspect library doesn't produce precise source locations,
|
||||
// we include the position information as part of the error message text.
|
||||
// See the comment inside method "Source" for more information.
|
||||
switch {
|
||||
case d.d.Pos == nil:
|
||||
return tfdiags.Description{
|
||||
Summary: d.d.Summary,
|
||||
Detail: d.d.Detail,
|
||||
}
|
||||
case d.d.Detail != "":
|
||||
return tfdiags.Description{
|
||||
Summary: d.d.Summary,
|
||||
Detail: fmt.Sprintf("On %s line %d: %s", d.d.Pos.Filename, d.d.Pos.Line, d.d.Detail),
|
||||
}
|
||||
default:
|
||||
return tfdiags.Description{
|
||||
Summary: fmt.Sprintf("%s (on %s line %d)", d.d.Summary, d.d.Pos.Filename, d.d.Pos.Line),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d wrappedDiagnostic) Source() tfdiags.Source {
|
||||
// Since the inspect library is constrained by the lowest common denominator
|
||||
// between legacy HCL and modern HCL, it only returns ranges at whole-line
|
||||
// granularity, and that isn't sufficient to populate a tfdiags.Source
|
||||
// and so we'll just omit ranges altogether and include the line number in
|
||||
// the Description text.
|
||||
//
|
||||
// Callers that want to return nicer errors should consider reacting to
|
||||
// earlyconfig errors by attempting a follow-up parse with the normal
|
||||
// config loader, which can produce more precise source location
|
||||
// information.
|
||||
return tfdiags.Source{}
|
||||
}
|
||||
|
||||
func (d wrappedDiagnostic) FromExpr() *tfdiags.FromExpr {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d wrappedDiagnostic) ExtraInfo() interface{} {
|
||||
return nil
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
// Package earlyconfig is a specialized alternative to the top-level "configs"
|
||||
// package that does only shallow processing of configuration and is therefore
|
||||
// able to be much more liberal than the full config loader in what it accepts.
|
||||
//
|
||||
// In particular, it can accept both current and legacy HCL syntax, and it
|
||||
// ignores top-level blocks that it doesn't recognize. These two characteristics
|
||||
// make this package ideal for dependency-checking use-cases so that we are
|
||||
// more likely to be able to return an error message about an explicit
|
||||
// incompatibility than to return a less-actionable message about a construct
|
||||
// not being supported.
|
||||
//
|
||||
// However, its liberal approach also means it should be used sparingly. It
|
||||
// exists primarily for "terraform init", so that it is able to detect
|
||||
// incompatibilities more robustly when installing dependencies. For most
|
||||
// other use-cases, use the "configs" and "configs/configload" packages.
|
||||
//
|
||||
// Package earlyconfig is a wrapper around the terraform-config-inspect
|
||||
// codebase, adding to it just some helper functionality for Terraform's own
|
||||
// use-cases.
|
||||
package earlyconfig
|
@ -1,13 +0,0 @@
|
||||
package earlyconfig
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/terraform-config-inspect/tfconfig"
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
)
|
||||
|
||||
// LoadModule loads some top-level metadata for the module in the given
|
||||
// directory.
|
||||
func LoadModule(dir string) (*tfconfig.Module, tfdiags.Diagnostics) {
|
||||
mod, diags := tfconfig.LoadModule(dir)
|
||||
return mod, wrapDiagnostics(diags)
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
cloud = {
|
||||
source = "tf.example.com/awesomecorp/happycloud"
|
||||
}
|
||||
null = {
|
||||
# This should merge with the null provider constraint in the root module
|
||||
version = "2.0.1"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
null = "~> 2.0.0"
|
||||
random = {
|
||||
version = "~> 1.2.0"
|
||||
}
|
||||
tls = {
|
||||
source = "hashicorp/tls"
|
||||
version = "~> 3.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# There is no provider in required_providers called "implied", so this
|
||||
# implicitly declares a dependency on "hashicorp/implied".
|
||||
resource "implied_foo" "bar" {
|
||||
}
|
||||
|
||||
module "child" {
|
||||
source = "./child"
|
||||
}
|
@ -10,18 +10,21 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/configs"
|
||||
"github.com/hashicorp/terraform/internal/configs/configload"
|
||||
"github.com/hashicorp/terraform/internal/copy"
|
||||
"github.com/hashicorp/terraform/internal/earlyconfig"
|
||||
"github.com/hashicorp/terraform/internal/getmodules"
|
||||
|
||||
version "github.com/hashicorp/go-version"
|
||||
"github.com/hashicorp/terraform-config-inspect/tfconfig"
|
||||
"github.com/hashicorp/terraform/internal/modsdir"
|
||||
"github.com/hashicorp/terraform/internal/registry"
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
)
|
||||
|
||||
const initFromModuleRootCallName = "root"
|
||||
const initFromModuleRootFilename = "<main configuration>"
|
||||
const initFromModuleRootKeyPrefix = initFromModuleRootCallName + "."
|
||||
|
||||
// DirFromModule populates the given directory (which must exist and be
|
||||
@ -42,7 +45,7 @@ const initFromModuleRootKeyPrefix = initFromModuleRootCallName + "."
|
||||
// references using ../ from that module to be unresolvable. Error diagnostics
|
||||
// are produced in that case, to prompt the user to rewrite the source strings
|
||||
// to be absolute references to the original remote module.
|
||||
func DirFromModule(ctx context.Context, rootDir, modulesDir, sourceAddr string, reg *registry.Client, hooks ModuleInstallHooks) tfdiags.Diagnostics {
|
||||
func DirFromModule(ctx context.Context, loader *configload.Loader, rootDir, modulesDir, sourceAddrStr string, reg *registry.Client, hooks ModuleInstallHooks) tfdiags.Diagnostics {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
// The way this function works is pretty ugly, but we accept it because
|
||||
@ -87,8 +90,8 @@ func DirFromModule(ctx context.Context, rootDir, modulesDir, sourceAddr string,
|
||||
}
|
||||
|
||||
instDir := filepath.Join(rootDir, ".terraform/init-from-module")
|
||||
inst := NewModuleInstaller(instDir, reg)
|
||||
log.Printf("[DEBUG] installing modules in %s to initialize working directory from %q", instDir, sourceAddr)
|
||||
inst := NewModuleInstaller(instDir, loader, reg)
|
||||
log.Printf("[DEBUG] installing modules in %s to initialize working directory from %q", instDir, sourceAddrStr)
|
||||
os.RemoveAll(instDir) // if this fails then we'll fail on MkdirAll below too
|
||||
err := os.MkdirAll(instDir, os.ModePerm)
|
||||
if err != nil {
|
||||
@ -103,12 +106,6 @@ func DirFromModule(ctx context.Context, rootDir, modulesDir, sourceAddr string,
|
||||
instManifest := make(modsdir.Manifest)
|
||||
retManifest := make(modsdir.Manifest)
|
||||
|
||||
fakeFilename := fmt.Sprintf("-from-module=%q", sourceAddr)
|
||||
fakePos := tfconfig.SourcePos{
|
||||
Filename: fakeFilename,
|
||||
Line: 1,
|
||||
}
|
||||
|
||||
// -from-module allows relative paths but it's different than a normal
|
||||
// module address where it'd be resolved relative to the module call
|
||||
// (which is synthetic, here.) To address this, we'll just patch up any
|
||||
@ -117,25 +114,38 @@ func DirFromModule(ctx context.Context, rootDir, modulesDir, sourceAddr string,
|
||||
// that the result will be "downloaded" with go-getter (copied from the
|
||||
// source location), rather than just recorded as a relative path.
|
||||
{
|
||||
maybePath := filepath.ToSlash(sourceAddr)
|
||||
maybePath := filepath.ToSlash(sourceAddrStr)
|
||||
if maybePath == "." || strings.HasPrefix(maybePath, "./") || strings.HasPrefix(maybePath, "../") {
|
||||
if wd, err := os.Getwd(); err == nil {
|
||||
sourceAddr = filepath.Join(wd, sourceAddr)
|
||||
log.Printf("[TRACE] -from-module relative path rewritten to absolute path %s", sourceAddr)
|
||||
sourceAddrStr = filepath.Join(wd, sourceAddrStr)
|
||||
log.Printf("[TRACE] -from-module relative path rewritten to absolute path %s", sourceAddrStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now we need to create an artificial root module that will seed our
|
||||
// installation process.
|
||||
fakeRootModule := &tfconfig.Module{
|
||||
ModuleCalls: map[string]*tfconfig.ModuleCall{
|
||||
sourceAddr, err := addrs.ParseModuleSource(sourceAddrStr)
|
||||
if err != nil {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Invalid module source address",
|
||||
fmt.Sprintf("Failed to parse module source address: %s", err),
|
||||
))
|
||||
}
|
||||
fakeRootModule := &configs.Module{
|
||||
ModuleCalls: map[string]*configs.ModuleCall{
|
||||
initFromModuleRootCallName: {
|
||||
Name: initFromModuleRootCallName,
|
||||
Source: sourceAddr,
|
||||
Pos: fakePos,
|
||||
Name: initFromModuleRootCallName,
|
||||
SourceAddr: sourceAddr,
|
||||
DeclRange: hcl.Range{
|
||||
Filename: initFromModuleRootFilename,
|
||||
Start: hcl.InitialPos,
|
||||
End: hcl.InitialPos,
|
||||
},
|
||||
},
|
||||
},
|
||||
ProviderRequirements: &configs.RequiredProviders{},
|
||||
}
|
||||
|
||||
// wrapHooks filters hook notifications to only include Download calls
|
||||
@ -181,7 +191,7 @@ func DirFromModule(ctx context.Context, rootDir, modulesDir, sourceAddr string,
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Failed to copy root module",
|
||||
fmt.Sprintf("Error copying root module %q from %s to %s: %s.", sourceAddr, record.Dir, rootDir, err),
|
||||
fmt.Sprintf("Error copying root module %q from %s to %s: %s.", sourceAddrStr, record.Dir, rootDir, err),
|
||||
))
|
||||
continue
|
||||
}
|
||||
@ -191,12 +201,12 @@ func DirFromModule(ctx context.Context, rootDir, modulesDir, sourceAddr string,
|
||||
// and must thus be rewritten to be absolute addresses again.
|
||||
// For now we can't do this rewriting automatically, but we'll
|
||||
// generate an error to help the user do it manually.
|
||||
mod, _ := earlyconfig.LoadModule(rootDir) // ignore diagnostics since we're just doing value-add here anyway
|
||||
mod, _ := loader.Parser().LoadConfigDir(rootDir) // ignore diagnostics since we're just doing value-add here anyway
|
||||
if mod != nil {
|
||||
for _, mc := range mod.ModuleCalls {
|
||||
if pathTraversesUp(mc.Source) {
|
||||
packageAddr, givenSubdir := getmodules.SplitPackageSubdir(sourceAddr)
|
||||
newSubdir := filepath.Join(givenSubdir, mc.Source)
|
||||
if pathTraversesUp(mc.SourceAddrRaw) {
|
||||
packageAddr, givenSubdir := getmodules.SplitPackageSubdir(sourceAddrStr)
|
||||
newSubdir := filepath.Join(givenSubdir, mc.SourceAddrRaw)
|
||||
if pathTraversesUp(newSubdir) {
|
||||
// This should never happen in any reasonable
|
||||
// configuration since this suggests a path that
|
||||
@ -214,7 +224,7 @@ func DirFromModule(ctx context.Context, rootDir, modulesDir, sourceAddr string,
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Root module references parent directory",
|
||||
fmt.Sprintf("The requested module %q refers to a module via its parent directory. To use this as a new root module this source string must be rewritten as a remote source address, such as %q.", sourceAddr, newAddr),
|
||||
fmt.Sprintf("The requested module %q refers to a module via its parent directory. To use this as a new root module this source string must be rewritten as a remote source address, such as %q.", sourceAddrStr, newAddr),
|
||||
))
|
||||
continue
|
||||
}
|
||||
|
@ -38,7 +38,9 @@ func TestDirFromModule_registry(t *testing.T) {
|
||||
hooks := &testInstallHooks{}
|
||||
|
||||
reg := registry.NewClient(nil, nil)
|
||||
diags := DirFromModule(context.Background(), dir, modsDir, "hashicorp/module-installer-acctest/aws//examples/main", reg, hooks)
|
||||
loader, cleanup := configload.NewLoaderForTests(t)
|
||||
defer cleanup()
|
||||
diags := DirFromModule(context.Background(), loader, dir, modsDir, "hashicorp/module-installer-acctest/aws//examples/main", reg, hooks)
|
||||
assertNoDiagnostics(t, diags)
|
||||
|
||||
v := version.Must(version.NewVersion("0.0.2"))
|
||||
@ -93,7 +95,7 @@ func TestDirFromModule_registry(t *testing.T) {
|
||||
t.Fatalf("wrong installer calls\n%s", diff)
|
||||
}
|
||||
|
||||
loader, err := configload.NewLoader(&configload.Config{
|
||||
loader, err = configload.NewLoader(&configload.Config{
|
||||
ModulesDir: modsDir,
|
||||
})
|
||||
if err != nil {
|
||||
@ -154,7 +156,9 @@ func TestDirFromModule_submodules(t *testing.T) {
|
||||
}
|
||||
modInstallDir := filepath.Join(dir, ".terraform/modules")
|
||||
|
||||
diags := DirFromModule(context.Background(), dir, modInstallDir, fromModuleDir, nil, hooks)
|
||||
loader, cleanup := configload.NewLoaderForTests(t)
|
||||
defer cleanup()
|
||||
diags := DirFromModule(context.Background(), loader, dir, modInstallDir, fromModuleDir, nil, hooks)
|
||||
assertNoDiagnostics(t, diags)
|
||||
wantCalls := []testInstallHookCall{
|
||||
{
|
||||
@ -173,7 +177,7 @@ func TestDirFromModule_submodules(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
loader, err := configload.NewLoader(&configload.Config{
|
||||
loader, err = configload.NewLoader(&configload.Config{
|
||||
ModulesDir: modInstallDir,
|
||||
})
|
||||
if err != nil {
|
||||
@ -246,7 +250,9 @@ func TestDirFromModule_rel_submodules(t *testing.T) {
|
||||
|
||||
modInstallDir := ".terraform/modules"
|
||||
sourceDir := "../local-modules"
|
||||
diags := DirFromModule(context.Background(), ".", modInstallDir, sourceDir, nil, hooks)
|
||||
loader, cleanup := configload.NewLoaderForTests(t)
|
||||
defer cleanup()
|
||||
diags := DirFromModule(context.Background(), loader, ".", modInstallDir, sourceDir, nil, hooks)
|
||||
assertNoDiagnostics(t, diags)
|
||||
wantCalls := []testInstallHookCall{
|
||||
{
|
||||
@ -265,7 +271,7 @@ func TestDirFromModule_rel_submodules(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
loader, err := configload.NewLoader(&configload.Config{
|
||||
loader, err = configload.NewLoader(&configload.Config{
|
||||
ModulesDir: modInstallDir,
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -1,56 +0,0 @@
|
||||
package initwd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
version "github.com/hashicorp/go-version"
|
||||
"github.com/hashicorp/terraform-config-inspect/tfconfig"
|
||||
"github.com/hashicorp/terraform/internal/earlyconfig"
|
||||
"github.com/hashicorp/terraform/internal/modsdir"
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
)
|
||||
|
||||
// LoadConfig loads a full configuration tree that has previously had all of
|
||||
// its dependent modules installed to the given modulesDir using a
|
||||
// ModuleInstaller.
|
||||
//
|
||||
// This uses the early configuration loader and thus only reads top-level
|
||||
// metadata from the modules in the configuration. Most callers should use
|
||||
// the configs/configload package to fully load a configuration.
|
||||
func LoadConfig(rootDir, modulesDir string) (*earlyconfig.Config, tfdiags.Diagnostics) {
|
||||
rootMod, diags := earlyconfig.LoadModule(rootDir)
|
||||
if rootMod == nil {
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
manifest, err := modsdir.ReadManifestSnapshotForDir(modulesDir)
|
||||
if err != nil {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Failed to read module manifest",
|
||||
fmt.Sprintf("Terraform failed to read its manifest of locally-cached modules: %s.", err),
|
||||
))
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
return earlyconfig.BuildConfig(rootMod, earlyconfig.ModuleWalkerFunc(
|
||||
func(req *earlyconfig.ModuleRequest) (*tfconfig.Module, *version.Version, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
key := manifest.ModuleKey(req.Path)
|
||||
record, exists := manifest[key]
|
||||
if !exists {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Module not installed",
|
||||
fmt.Sprintf("Module %s is not yet installed. Run \"terraform init\" to install all modules required by this configuration.", req.Path.String()),
|
||||
))
|
||||
return nil, nil, diags
|
||||
}
|
||||
|
||||
mod, mDiags := earlyconfig.LoadModule(record.Dir)
|
||||
diags = diags.Append(mDiags)
|
||||
return mod, record.Version, diags
|
||||
},
|
||||
))
|
||||
}
|
@ -12,10 +12,11 @@ import (
|
||||
|
||||
"github.com/apparentlymart/go-versions/versions"
|
||||
version "github.com/hashicorp/go-version"
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
|
||||
"github.com/hashicorp/terraform-config-inspect/tfconfig"
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/earlyconfig"
|
||||
"github.com/hashicorp/terraform/internal/configs"
|
||||
"github.com/hashicorp/terraform/internal/configs/configload"
|
||||
"github.com/hashicorp/terraform/internal/getmodules"
|
||||
"github.com/hashicorp/terraform/internal/modsdir"
|
||||
"github.com/hashicorp/terraform/internal/registry"
|
||||
@ -26,6 +27,7 @@ import (
|
||||
|
||||
type ModuleInstaller struct {
|
||||
modsDir string
|
||||
loader *configload.Loader
|
||||
reg *registry.Client
|
||||
|
||||
// The keys in moduleVersions are resolved and trimmed registry source
|
||||
@ -42,9 +44,10 @@ type moduleVersion struct {
|
||||
version string
|
||||
}
|
||||
|
||||
func NewModuleInstaller(modsDir string, reg *registry.Client) *ModuleInstaller {
|
||||
func NewModuleInstaller(modsDir string, loader *configload.Loader, reg *registry.Client) *ModuleInstaller {
|
||||
return &ModuleInstaller{
|
||||
modsDir: modsDir,
|
||||
loader: loader,
|
||||
reg: reg,
|
||||
registryPackageVersions: make(map[addrs.ModuleRegistryPackage]*response.ModuleVersions),
|
||||
registryPackageSources: make(map[moduleVersion]addrs.ModuleSourceRemote),
|
||||
@ -79,12 +82,23 @@ func NewModuleInstaller(modsDir string, reg *registry.Client) *ModuleInstaller {
|
||||
// If successful (the returned diagnostics contains no errors) then the
|
||||
// first return value is the early configuration tree that was constructed by
|
||||
// the installation process.
|
||||
func (i *ModuleInstaller) InstallModules(ctx context.Context, rootDir string, upgrade bool, hooks ModuleInstallHooks) (*earlyconfig.Config, tfdiags.Diagnostics) {
|
||||
func (i *ModuleInstaller) InstallModules(ctx context.Context, rootDir string, upgrade bool, hooks ModuleInstallHooks) (*configs.Config, tfdiags.Diagnostics) {
|
||||
log.Printf("[TRACE] ModuleInstaller: installing child modules for %s into %s", rootDir, i.modsDir)
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
rootMod, diags := earlyconfig.LoadModule(rootDir)
|
||||
rootMod, mDiags := i.loader.Parser().LoadConfigDir(rootDir)
|
||||
if rootMod == nil {
|
||||
// We drop the diagnostics here because we only want to report module
|
||||
// loading errors after checking the core version constraints, which we
|
||||
// can only do if the module can be at least partially loaded.
|
||||
return nil, diags
|
||||
} else if vDiags := rootMod.CheckCoreVersionRequirements(nil, nil); vDiags.HasErrors() {
|
||||
// If the core version requirements are not met, we drop any other
|
||||
// diagnostics, as they may reflect language changes from future
|
||||
// Terraform versions.
|
||||
diags = diags.Append(vDiags)
|
||||
} else {
|
||||
diags = diags.Append(mDiags)
|
||||
}
|
||||
|
||||
manifest, err := modsdir.ReadManifestSnapshotForDir(i.modsDir)
|
||||
@ -104,7 +118,7 @@ func (i *ModuleInstaller) InstallModules(ctx context.Context, rootDir string, up
|
||||
return cfg, diags
|
||||
}
|
||||
|
||||
func (i *ModuleInstaller) installDescendentModules(ctx context.Context, rootMod *tfconfig.Module, rootDir string, manifest modsdir.Manifest, upgrade bool, hooks ModuleInstallHooks, fetcher *getmodules.PackageFetcher) (*earlyconfig.Config, tfdiags.Diagnostics) {
|
||||
func (i *ModuleInstaller) installDescendentModules(ctx context.Context, rootMod *configs.Module, rootDir string, manifest modsdir.Manifest, upgrade bool, hooks ModuleInstallHooks, fetcher *getmodules.PackageFetcher) (*configs.Config, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
if hooks == nil {
|
||||
@ -119,8 +133,25 @@ func (i *ModuleInstaller) installDescendentModules(ctx context.Context, rootMod
|
||||
Dir: rootDir,
|
||||
}
|
||||
|
||||
cfg, cDiags := earlyconfig.BuildConfig(rootMod, earlyconfig.ModuleWalkerFunc(
|
||||
func(req *earlyconfig.ModuleRequest) (*tfconfig.Module, *version.Version, tfdiags.Diagnostics) {
|
||||
cfg, cDiags := configs.BuildConfig(rootMod, configs.ModuleWalkerFunc(
|
||||
func(req *configs.ModuleRequest) (*configs.Module, *version.Version, hcl.Diagnostics) {
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
if req.SourceAddr == nil {
|
||||
// If the parent module failed to parse the module source
|
||||
// address, we can't load it here. Return nothing as the parent
|
||||
// module's diagnostics should explain this.
|
||||
return nil, nil, diags
|
||||
}
|
||||
|
||||
if req.Name == "" {
|
||||
// An empty string for a module instance name breaks our
|
||||
// manifest map, which uses that to indicate the root module.
|
||||
// Because we descend into modules which have errors, we need
|
||||
// to look out for this case, but the config loader's
|
||||
// diagnostics will report the error later.
|
||||
return nil, nil, diags
|
||||
}
|
||||
|
||||
key := manifest.ModuleKey(req.Path)
|
||||
instPath := i.packageInstallPath(req.Path)
|
||||
@ -139,8 +170,8 @@ func (i *ModuleInstaller) installDescendentModules(ctx context.Context, rootMod
|
||||
case record.SourceAddr != req.SourceAddr.String():
|
||||
log.Printf("[TRACE] ModuleInstaller: %s source address has changed from %q to %q", key, record.SourceAddr, req.SourceAddr)
|
||||
replace = true
|
||||
case record.Version != nil && !req.VersionConstraints.Check(record.Version):
|
||||
log.Printf("[TRACE] ModuleInstaller: %s version %s no longer compatible with constraints %s", key, record.Version, req.VersionConstraints)
|
||||
case record.Version != nil && !req.VersionConstraint.Required.Check(record.Version):
|
||||
log.Printf("[TRACE] ModuleInstaller: %s version %s no longer compatible with constraints %s", key, record.Version, req.VersionConstraint.Required)
|
||||
replace = true
|
||||
}
|
||||
}
|
||||
@ -174,14 +205,14 @@ func (i *ModuleInstaller) installDescendentModules(ctx context.Context, rootMod
|
||||
err := os.RemoveAll(instPath)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
log.Printf("[TRACE] ModuleInstaller: failed to remove %s: %s", key, err)
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Failed to remove local module cache",
|
||||
fmt.Sprintf(
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Failed to remove local module cache",
|
||||
Detail: fmt.Sprintf(
|
||||
"Terraform tried to remove %s in order to reinstall this module, but encountered an error: %s",
|
||||
instPath, err,
|
||||
),
|
||||
))
|
||||
})
|
||||
return nil, nil, diags
|
||||
}
|
||||
} else {
|
||||
@ -190,8 +221,19 @@ func (i *ModuleInstaller) installDescendentModules(ctx context.Context, rootMod
|
||||
// keep our existing record.
|
||||
info, err := os.Stat(record.Dir)
|
||||
if err == nil && info.IsDir() {
|
||||
mod, mDiags := earlyconfig.LoadModule(record.Dir)
|
||||
diags = diags.Append(mDiags)
|
||||
mod, mDiags := i.loader.Parser().LoadConfigDir(record.Dir)
|
||||
if mod == nil {
|
||||
// nil indicates an unreadable module, which should never happen,
|
||||
// so we return the full loader diagnostics here.
|
||||
diags = diags.Extend(mDiags)
|
||||
} else if vDiags := mod.CheckCoreVersionRequirements(req.Path, req.SourceAddr); vDiags.HasErrors() {
|
||||
// If the core version requirements are not met, we drop any other
|
||||
// diagnostics, as they may reflect language changes from future
|
||||
// Terraform versions.
|
||||
diags = diags.Extend(vDiags)
|
||||
} else {
|
||||
diags = diags.Extend(mDiags)
|
||||
}
|
||||
|
||||
log.Printf("[TRACE] ModuleInstaller: Module installer: %s %s already installed in %s", key, record.Version, record.Dir)
|
||||
return mod, record.Version, diags
|
||||
@ -231,7 +273,7 @@ func (i *ModuleInstaller) installDescendentModules(ctx context.Context, rootMod
|
||||
|
||||
},
|
||||
))
|
||||
diags = append(diags, cDiags...)
|
||||
diags = diags.Append(cDiags)
|
||||
|
||||
err := manifest.WriteSnapshotToDir(i.modsDir)
|
||||
if err != nil {
|
||||
@ -245,8 +287,8 @@ func (i *ModuleInstaller) installDescendentModules(ctx context.Context, rootMod
|
||||
return cfg, diags
|
||||
}
|
||||
|
||||
func (i *ModuleInstaller) installLocalModule(req *earlyconfig.ModuleRequest, key string, manifest modsdir.Manifest, hooks ModuleInstallHooks) (*tfconfig.Module, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
func (i *ModuleInstaller) installLocalModule(req *configs.ModuleRequest, key string, manifest modsdir.Manifest, hooks ModuleInstallHooks) (*configs.Module, hcl.Diagnostics) {
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
parentKey := manifest.ModuleKey(req.Parent.Path)
|
||||
parentRecord, recorded := manifest[parentKey]
|
||||
@ -255,12 +297,13 @@ func (i *ModuleInstaller) installLocalModule(req *earlyconfig.ModuleRequest, key
|
||||
panic(fmt.Errorf("missing manifest record for parent module %s", parentKey))
|
||||
}
|
||||
|
||||
if len(req.VersionConstraints) != 0 {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Invalid version constraint",
|
||||
fmt.Sprintf("Cannot apply a version constraint to module %q (at %s:%d) because it has a relative local path.", req.Name, req.CallPos.Filename, req.CallPos.Line),
|
||||
))
|
||||
if len(req.VersionConstraint.Required) != 0 {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid version constraint",
|
||||
Detail: fmt.Sprintf("Cannot apply a version constraint to module %q (at %s:%d) because it has a relative local path.", req.Name, req.CallRange.Filename, req.CallRange.Start.Line),
|
||||
Subject: req.CallRange.Ptr(),
|
||||
})
|
||||
}
|
||||
|
||||
// For local sources we don't actually need to modify the
|
||||
@ -272,25 +315,31 @@ func (i *ModuleInstaller) installLocalModule(req *earlyconfig.ModuleRequest, key
|
||||
// it is possible that the local directory is a symlink
|
||||
newDir, err := filepath.EvalSymlinks(newDir)
|
||||
if err != nil {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Unreadable module directory",
|
||||
fmt.Sprintf("Unable to evaluate directory symlink: %s", err.Error()),
|
||||
))
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unreadable module directory",
|
||||
Detail: fmt.Sprintf("Unable to evaluate directory symlink: %s", err.Error()),
|
||||
})
|
||||
}
|
||||
|
||||
mod, mDiags := earlyconfig.LoadModule(newDir)
|
||||
// Finally we are ready to try actually loading the module.
|
||||
mod, mDiags := i.loader.Parser().LoadConfigDir(newDir)
|
||||
if mod == nil {
|
||||
// nil indicates missing or unreadable directory, so we'll
|
||||
// discard the returned diags and return a more specific
|
||||
// error message here.
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Unreadable module directory",
|
||||
fmt.Sprintf("The directory %s could not be read for module %q at %s:%d.", newDir, req.Name, req.CallPos.Filename, req.CallPos.Line),
|
||||
))
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unreadable module directory",
|
||||
Detail: fmt.Sprintf("The directory %s could not be read for module %q at %s:%d.", newDir, req.Name, req.CallRange.Filename, req.CallRange.Start.Line),
|
||||
})
|
||||
} else if vDiags := mod.CheckCoreVersionRequirements(req.Path, req.SourceAddr); vDiags.HasErrors() {
|
||||
// If the core version requirements are not met, we drop any other
|
||||
// diagnostics, as they may reflect language changes from future
|
||||
// Terraform versions.
|
||||
diags = diags.Extend(vDiags)
|
||||
} else {
|
||||
diags = diags.Append(mDiags)
|
||||
diags = diags.Extend(mDiags)
|
||||
}
|
||||
|
||||
// Note the local location in our manifest.
|
||||
@ -305,8 +354,8 @@ func (i *ModuleInstaller) installLocalModule(req *earlyconfig.ModuleRequest, key
|
||||
return mod, diags
|
||||
}
|
||||
|
||||
func (i *ModuleInstaller) installRegistryModule(ctx context.Context, req *earlyconfig.ModuleRequest, key string, instPath string, addr addrs.ModuleSourceRegistry, manifest modsdir.Manifest, hooks ModuleInstallHooks, fetcher *getmodules.PackageFetcher) (*tfconfig.Module, *version.Version, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
func (i *ModuleInstaller) installRegistryModule(ctx context.Context, req *configs.ModuleRequest, key string, instPath string, addr addrs.ModuleSourceRegistry, manifest modsdir.Manifest, hooks ModuleInstallHooks, fetcher *getmodules.PackageFetcher) (*configs.Module, *version.Version, hcl.Diagnostics) {
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
hostname := addr.Package.Host
|
||||
reg := i.reg
|
||||
@ -331,23 +380,25 @@ func (i *ModuleInstaller) installRegistryModule(ctx context.Context, req *earlyc
|
||||
resp, err = reg.ModuleVersions(ctx, regsrcAddr)
|
||||
if err != nil {
|
||||
if registry.IsModuleNotFound(err) {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Module not found",
|
||||
fmt.Sprintf("Module %q (from %s:%d) cannot be found in the module registry at %s.", req.Name, req.CallPos.Filename, req.CallPos.Line, hostname),
|
||||
))
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Module not found",
|
||||
Detail: fmt.Sprintf("Module %q (from %s:%d) cannot be found in the module registry at %s.", req.Name, req.CallRange.Filename, req.CallRange.Start.Line, hostname),
|
||||
Subject: req.CallRange.Ptr(),
|
||||
})
|
||||
} else if errors.Is(err, context.Canceled) {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Module installation was interrupted",
|
||||
fmt.Sprintf("Received interrupt signal while retrieving available versions for module %q.", req.Name),
|
||||
))
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Module installation was interrupted",
|
||||
Detail: fmt.Sprintf("Received interrupt signal while retrieving available versions for module %q.", req.Name),
|
||||
})
|
||||
} else {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Error accessing remote module registry",
|
||||
fmt.Sprintf("Failed to retrieve available versions for module %q (%s:%d) from %s: %s.", req.Name, req.CallPos.Filename, req.CallPos.Line, hostname, err),
|
||||
))
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Error accessing remote module registry",
|
||||
Detail: fmt.Sprintf("Failed to retrieve available versions for module %q (%s:%d) from %s: %s.", req.Name, req.CallRange.Filename, req.CallRange.Start.Line, hostname, err),
|
||||
Subject: req.CallRange.Ptr(),
|
||||
})
|
||||
}
|
||||
return nil, nil, diags
|
||||
}
|
||||
@ -361,11 +412,12 @@ func (i *ModuleInstaller) installRegistryModule(ctx context.Context, req *earlyc
|
||||
if len(resp.Modules) < 1 {
|
||||
// Should never happen, but since this is a remote service that may
|
||||
// be implemented by third-parties we will handle it gracefully.
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Invalid response from remote module registry",
|
||||
fmt.Sprintf("The registry at %s returned an invalid response when Terraform requested available versions for module %q (%s:%d).", hostname, req.Name, req.CallPos.Filename, req.CallPos.Line),
|
||||
))
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid response from remote module registry",
|
||||
Detail: fmt.Sprintf("The registry at %s returned an invalid response when Terraform requested available versions for module %q (%s:%d).", hostname, req.Name, req.CallRange.Filename, req.CallRange.Start.Line),
|
||||
Subject: req.CallRange.Ptr(),
|
||||
})
|
||||
return nil, nil, diags
|
||||
}
|
||||
|
||||
@ -379,11 +431,12 @@ func (i *ModuleInstaller) installRegistryModule(ctx context.Context, req *earlyc
|
||||
// Should never happen if the registry server is compliant with
|
||||
// the protocol, but we'll warn if not to assist someone who
|
||||
// might be developing a module registry server.
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Warning,
|
||||
"Invalid response from remote module registry",
|
||||
fmt.Sprintf("The registry at %s returned an invalid version string %q for module %q (%s:%d), which Terraform ignored.", hostname, mv.Version, req.Name, req.CallPos.Filename, req.CallPos.Line),
|
||||
))
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagWarning,
|
||||
Summary: "Invalid response from remote module registry",
|
||||
Detail: fmt.Sprintf("The registry at %s returned an invalid version string %q for module %q (%s:%d), which Terraform ignored.", hostname, mv.Version, req.Name, req.CallRange.Filename, req.CallRange.Start.Line),
|
||||
Subject: req.CallRange.Ptr(),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
@ -401,9 +454,9 @@ func (i *ModuleInstaller) installRegistryModule(ctx context.Context, req *earlyc
|
||||
// prerelease metadata will be checked. Users may not have even
|
||||
// requested this prerelease so don't print lots of unnecessary #
|
||||
// warnings.
|
||||
acceptableVersions, err := versions.MeetingConstraintsString(req.VersionConstraints.String())
|
||||
acceptableVersions, err := versions.MeetingConstraintsString(req.VersionConstraint.Required.String())
|
||||
if err != nil {
|
||||
log.Printf("[WARN] ModuleInstaller: %s ignoring %s because the version constraints (%s) could not be parsed: %s", key, v, req.VersionConstraints.String(), err.Error())
|
||||
log.Printf("[WARN] ModuleInstaller: %s ignoring %s because the version constraints (%s) could not be parsed: %s", key, v, req.VersionConstraint.Required.String(), err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
@ -434,7 +487,7 @@ func (i *ModuleInstaller) installRegistryModule(ctx context.Context, req *earlyc
|
||||
latestVersion = v
|
||||
}
|
||||
|
||||
if req.VersionConstraints.Check(v) {
|
||||
if req.VersionConstraint.Required.Check(v) {
|
||||
if latestMatch == nil || v.GreaterThan(latestMatch) {
|
||||
latestMatch = v
|
||||
}
|
||||
@ -442,20 +495,22 @@ func (i *ModuleInstaller) installRegistryModule(ctx context.Context, req *earlyc
|
||||
}
|
||||
|
||||
if latestVersion == nil {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Module has no versions",
|
||||
fmt.Sprintf("Module %q (%s:%d) has no versions available on %s.", addr, req.CallPos.Filename, req.CallPos.Line, hostname),
|
||||
))
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Module has no versions",
|
||||
Detail: fmt.Sprintf("Module %q (%s:%d) has no versions available on %s.", addr, req.CallRange.Filename, req.CallRange.Start.Line, hostname),
|
||||
Subject: req.CallRange.Ptr(),
|
||||
})
|
||||
return nil, nil, diags
|
||||
}
|
||||
|
||||
if latestMatch == nil {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Unresolvable module version constraint",
|
||||
fmt.Sprintf("There is no available version of module %q (%s:%d) which matches the given version constraint. The newest available version is %s.", addr, req.CallPos.Filename, req.CallPos.Line, latestVersion),
|
||||
))
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unresolvable module version constraint",
|
||||
Detail: fmt.Sprintf("There is no available version of module %q (%s:%d) which matches the given version constraint. The newest available version is %s.", addr, req.CallRange.Filename, req.CallRange.Start.Line, latestVersion),
|
||||
Subject: req.CallRange.Ptr(),
|
||||
})
|
||||
return nil, nil, diags
|
||||
}
|
||||
|
||||
@ -472,20 +527,20 @@ func (i *ModuleInstaller) installRegistryModule(ctx context.Context, req *earlyc
|
||||
realAddrRaw, err := reg.ModuleLocation(ctx, regsrcAddr, latestMatch.String())
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] %s from %s %s: %s", key, addr, latestMatch, err)
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Error accessing remote module registry",
|
||||
fmt.Sprintf("Failed to retrieve a download URL for %s %s from %s: %s", addr, latestMatch, hostname, err),
|
||||
))
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Error accessing remote module registry",
|
||||
Detail: fmt.Sprintf("Failed to retrieve a download URL for %s %s from %s: %s", addr, latestMatch, hostname, err),
|
||||
})
|
||||
return nil, nil, diags
|
||||
}
|
||||
realAddr, err := addrs.ParseModuleSource(realAddrRaw)
|
||||
if err != nil {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Invalid package location from module registry",
|
||||
fmt.Sprintf("Module registry %s returned invalid source location %q for %s %s: %s.", hostname, realAddrRaw, addr, latestMatch, err),
|
||||
))
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid package location from module registry",
|
||||
Detail: fmt.Sprintf("Module registry %s returned invalid source location %q for %s %s: %s.", hostname, realAddrRaw, addr, latestMatch, err),
|
||||
})
|
||||
return nil, nil, diags
|
||||
}
|
||||
switch realAddr := realAddr.(type) {
|
||||
@ -496,11 +551,11 @@ func (i *ModuleInstaller) installRegistryModule(ctx context.Context, req *earlyc
|
||||
case addrs.ModuleSourceRemote:
|
||||
i.registryPackageSources[moduleAddr] = realAddr
|
||||
default:
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Invalid package location from module registry",
|
||||
fmt.Sprintf("Module registry %s returned invalid source location %q for %s %s: must be a direct remote package address.", hostname, realAddrRaw, addr, latestMatch),
|
||||
))
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid package location from module registry",
|
||||
Detail: fmt.Sprintf("Module registry %s returned invalid source location %q for %s %s: must be a direct remote package address.", hostname, realAddrRaw, addr, latestMatch),
|
||||
})
|
||||
return nil, nil, diags
|
||||
}
|
||||
}
|
||||
@ -511,11 +566,11 @@ func (i *ModuleInstaller) installRegistryModule(ctx context.Context, req *earlyc
|
||||
|
||||
err := fetcher.FetchPackage(ctx, instPath, dlAddr.Package.String())
|
||||
if errors.Is(err, context.Canceled) {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Module download was interrupted",
|
||||
fmt.Sprintf("Interrupt signal received when downloading module %s.", addr),
|
||||
))
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Module download was interrupted",
|
||||
Detail: fmt.Sprintf("Interrupt signal received when downloading module %s.", addr),
|
||||
})
|
||||
return nil, nil, diags
|
||||
}
|
||||
if err != nil {
|
||||
@ -524,11 +579,12 @@ func (i *ModuleInstaller) installRegistryModule(ctx context.Context, req *earlyc
|
||||
// we have no way to recognize any specific errors to improve them
|
||||
// and masking the error entirely would hide valuable diagnostic
|
||||
// information from the user.
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Failed to download module",
|
||||
fmt.Sprintf("Could not download module %q (%s:%d) source code from %q: %s.", req.Name, req.CallPos.Filename, req.CallPos.Line, dlAddr, err),
|
||||
))
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Failed to download module",
|
||||
Detail: fmt.Sprintf("Could not download module %q (%s:%d) source code from %q: %s.", req.Name, req.CallRange.Filename, req.CallRange.Start.Line, dlAddr, err),
|
||||
Subject: req.CallRange.Ptr(),
|
||||
})
|
||||
return nil, nil, diags
|
||||
}
|
||||
|
||||
@ -544,20 +600,25 @@ func (i *ModuleInstaller) installRegistryModule(ctx context.Context, req *earlyc
|
||||
log.Printf("[TRACE] ModuleInstaller: %s should now be at %s", key, modDir)
|
||||
|
||||
// Finally we are ready to try actually loading the module.
|
||||
mod, mDiags := earlyconfig.LoadModule(modDir)
|
||||
mod, mDiags := i.loader.Parser().LoadConfigDir(modDir)
|
||||
if mod == nil {
|
||||
// nil indicates missing or unreadable directory, so we'll
|
||||
// discard the returned diags and return a more specific
|
||||
// error message here. For registry modules this actually
|
||||
// indicates a bug in the code above, since it's not the
|
||||
// user's responsibility to create the directory in this case.
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Unreadable module directory",
|
||||
fmt.Sprintf("The directory %s could not be read. This is a bug in Terraform and should be reported.", modDir),
|
||||
))
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unreadable module directory",
|
||||
Detail: fmt.Sprintf("The directory %s could not be read. This is a bug in Terraform and should be reported.", modDir),
|
||||
})
|
||||
} else if vDiags := mod.CheckCoreVersionRequirements(req.Path, req.SourceAddr); vDiags.HasErrors() {
|
||||
// If the core version requirements are not met, we drop any other
|
||||
// diagnostics, as they may reflect language changes from future
|
||||
// Terraform versions.
|
||||
diags = diags.Extend(vDiags)
|
||||
} else {
|
||||
diags = append(diags, mDiags...)
|
||||
diags = diags.Extend(mDiags)
|
||||
}
|
||||
|
||||
// Note the local location in our manifest.
|
||||
@ -573,20 +634,21 @@ func (i *ModuleInstaller) installRegistryModule(ctx context.Context, req *earlyc
|
||||
return mod, latestMatch, diags
|
||||
}
|
||||
|
||||
func (i *ModuleInstaller) installGoGetterModule(ctx context.Context, req *earlyconfig.ModuleRequest, key string, instPath string, manifest modsdir.Manifest, hooks ModuleInstallHooks, fetcher *getmodules.PackageFetcher) (*tfconfig.Module, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
func (i *ModuleInstaller) installGoGetterModule(ctx context.Context, req *configs.ModuleRequest, key string, instPath string, manifest modsdir.Manifest, hooks ModuleInstallHooks, fetcher *getmodules.PackageFetcher) (*configs.Module, hcl.Diagnostics) {
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
// Report up to the caller that we're about to start downloading.
|
||||
addr := req.SourceAddr.(addrs.ModuleSourceRemote)
|
||||
packageAddr := addr.Package
|
||||
hooks.Download(key, packageAddr.String(), nil)
|
||||
|
||||
if len(req.VersionConstraints) != 0 {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Invalid version constraint",
|
||||
fmt.Sprintf("Cannot apply a version constraint to module %q (at %s:%d) because it doesn't come from a module registry.", req.Name, req.CallPos.Filename, req.CallPos.Line),
|
||||
))
|
||||
if len(req.VersionConstraint.Required) != 0 {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid version constraint",
|
||||
Detail: fmt.Sprintf("Cannot apply a version constraint to module %q (at %s:%d) because it doesn't come from a module registry.", req.Name, req.CallRange.Filename, req.CallRange.Start.Line),
|
||||
Subject: req.CallRange.Ptr(),
|
||||
})
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
@ -599,54 +661,65 @@ func (i *ModuleInstaller) installGoGetterModule(ctx context.Context, req *earlyc
|
||||
"[TRACE] ModuleInstaller: %s looks like a local path but is missing ./ or ../",
|
||||
req.SourceAddr,
|
||||
)
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Module not found",
|
||||
fmt.Sprintf(
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Module not found",
|
||||
Detail: fmt.Sprintf(
|
||||
"The module address %q could not be resolved.\n\n"+
|
||||
"If you intended this as a path relative to the current "+
|
||||
"module, use \"./%s\" instead. The \"./\" prefix "+
|
||||
"indicates that the address is a relative filesystem path.",
|
||||
req.SourceAddr, req.SourceAddr,
|
||||
),
|
||||
))
|
||||
})
|
||||
} else {
|
||||
// Errors returned by go-getter have very inconsistent quality as
|
||||
// end-user error messages, but for now we're accepting that because
|
||||
// we have no way to recognize any specific errors to improve them
|
||||
// and masking the error entirely would hide valuable diagnostic
|
||||
// information from the user.
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Failed to download module",
|
||||
fmt.Sprintf("Could not download module %q (%s:%d) source code from %q: %s", req.Name, req.CallPos.Filename, req.CallPos.Line, packageAddr, err),
|
||||
))
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Failed to download module",
|
||||
Detail: fmt.Sprintf("Could not download module %q (%s:%d) source code from %q: %s", req.Name, req.CallRange.Filename, req.CallRange.Start.Line, packageAddr, err),
|
||||
Subject: req.CallRange.Ptr(),
|
||||
})
|
||||
}
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
modDir, err := getmodules.ExpandSubdirGlobs(instPath, addr.Subdir)
|
||||
if err != nil {
|
||||
diags = diags.Append(err)
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Failed to expand subdir globs",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
log.Printf("[TRACE] ModuleInstaller: %s %q was downloaded to %s", key, addr, modDir)
|
||||
|
||||
mod, mDiags := earlyconfig.LoadModule(modDir)
|
||||
// Finally we are ready to try actually loading the module.
|
||||
mod, mDiags := i.loader.Parser().LoadConfigDir(modDir)
|
||||
if mod == nil {
|
||||
// nil indicates missing or unreadable directory, so we'll
|
||||
// discard the returned diags and return a more specific
|
||||
// error message here. For go-getter modules this actually
|
||||
// indicates a bug in the code above, since it's not the
|
||||
// user's responsibility to create the directory in this case.
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Unreadable module directory",
|
||||
fmt.Sprintf("The directory %s could not be read. This is a bug in Terraform and should be reported.", modDir),
|
||||
))
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unreadable module directory",
|
||||
Detail: fmt.Sprintf("The directory %s could not be read. This is a bug in Terraform and should be reported.", modDir),
|
||||
})
|
||||
} else if vDiags := mod.CheckCoreVersionRequirements(req.Path, req.SourceAddr); vDiags.HasErrors() {
|
||||
// If the core version requirements are not met, we drop any other
|
||||
// diagnostics, as they may reflect language changes from future
|
||||
// Terraform versions.
|
||||
diags = diags.Extend(vDiags)
|
||||
} else {
|
||||
diags = append(diags, mDiags...)
|
||||
diags = diags.Extend(mDiags)
|
||||
}
|
||||
|
||||
// Note the local location in our manifest.
|
||||
@ -674,7 +747,7 @@ func (i *ModuleInstaller) packageInstallPath(modulePath addrs.Module) string {
|
||||
//
|
||||
// This function's behavior is only reasonable for errors returned from the
|
||||
// ModuleInstaller.installLocalModule function.
|
||||
func maybeImproveLocalInstallError(req *earlyconfig.ModuleRequest, diags tfdiags.Diagnostics) tfdiags.Diagnostics {
|
||||
func maybeImproveLocalInstallError(req *configs.ModuleRequest, diags hcl.Diagnostics) hcl.Diagnostics {
|
||||
if !diags.HasErrors() {
|
||||
return diags
|
||||
}
|
||||
@ -709,7 +782,7 @@ func maybeImproveLocalInstallError(req *earlyconfig.ModuleRequest, diags tfdiags
|
||||
Path: req.Path,
|
||||
SourceAddr: req.SourceAddr,
|
||||
})
|
||||
current := req.Parent // an earlyconfig.Config where Children isn't populated yet
|
||||
current := req.Parent // a configs.Config where Children isn't populated yet
|
||||
for {
|
||||
if current == nil || current.SourceAddr == nil {
|
||||
// We've reached the root module, in which case we aren't
|
||||
@ -753,11 +826,11 @@ func maybeImproveLocalInstallError(req *earlyconfig.ModuleRequest, diags tfdiags
|
||||
if !strings.HasPrefix(nextPath, prefix) { // ESCAPED!
|
||||
escapeeAddr := step.Path.String()
|
||||
|
||||
var newDiags tfdiags.Diagnostics
|
||||
var newDiags hcl.Diagnostics
|
||||
|
||||
// First we'll copy over any non-error diagnostics from the source diags
|
||||
for _, diag := range diags {
|
||||
if diag.Severity() != tfdiags.Error {
|
||||
if diag.Severity != hcl.DiagError {
|
||||
newDiags = newDiags.Append(diag)
|
||||
}
|
||||
}
|
||||
@ -772,14 +845,14 @@ func maybeImproveLocalInstallError(req *earlyconfig.ModuleRequest, diags tfdiags
|
||||
// about it.
|
||||
suggestion = "\n\nTerraform treats absolute filesystem paths as external modules which establish a new module package. To treat this directory as part of the same package as its caller, use a local path starting with either \"./\" or \"../\"."
|
||||
}
|
||||
newDiags = newDiags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Local module path escapes module package",
|
||||
fmt.Sprintf(
|
||||
newDiags = newDiags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Local module path escapes module package",
|
||||
Detail: fmt.Sprintf(
|
||||
"The given source directory for %s would be outside of its containing package %q. Local source addresses starting with \"../\" must stay within the same package that the calling module belongs to.%s",
|
||||
escapeeAddr, packageAddr, suggestion,
|
||||
),
|
||||
))
|
||||
})
|
||||
|
||||
return newDiags
|
||||
}
|
||||
|
@ -39,7 +39,9 @@ func TestModuleInstaller(t *testing.T) {
|
||||
hooks := &testInstallHooks{}
|
||||
|
||||
modulesDir := filepath.Join(dir, ".terraform/modules")
|
||||
inst := NewModuleInstaller(modulesDir, nil)
|
||||
loader, close := configload.NewLoaderForTests(t)
|
||||
defer close()
|
||||
inst := NewModuleInstaller(modulesDir, loader, nil)
|
||||
_, diags := inst.InstallModules(context.Background(), ".", false, hooks)
|
||||
assertNoDiagnostics(t, diags)
|
||||
|
||||
@ -100,7 +102,10 @@ func TestModuleInstaller_error(t *testing.T) {
|
||||
hooks := &testInstallHooks{}
|
||||
|
||||
modulesDir := filepath.Join(dir, ".terraform/modules")
|
||||
inst := NewModuleInstaller(modulesDir, nil)
|
||||
|
||||
loader, close := configload.NewLoaderForTests(t)
|
||||
defer close()
|
||||
inst := NewModuleInstaller(modulesDir, loader, nil)
|
||||
_, diags := inst.InstallModules(context.Background(), ".", false, hooks)
|
||||
|
||||
if !diags.HasErrors() {
|
||||
@ -110,6 +115,27 @@ func TestModuleInstaller_error(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestModuleInstaller_emptyModuleName(t *testing.T) {
|
||||
fixtureDir := filepath.Clean("testdata/empty-module-name")
|
||||
dir, done := tempChdir(t, fixtureDir)
|
||||
defer done()
|
||||
|
||||
hooks := &testInstallHooks{}
|
||||
|
||||
modulesDir := filepath.Join(dir, ".terraform/modules")
|
||||
|
||||
loader, close := configload.NewLoaderForTests(t)
|
||||
defer close()
|
||||
inst := NewModuleInstaller(modulesDir, loader, nil)
|
||||
_, diags := inst.InstallModules(context.Background(), ".", false, hooks)
|
||||
|
||||
if !diags.HasErrors() {
|
||||
t.Fatal("expected error")
|
||||
} else {
|
||||
assertDiagnosticSummary(t, diags, "Invalid module instance name")
|
||||
}
|
||||
}
|
||||
|
||||
func TestModuleInstaller_packageEscapeError(t *testing.T) {
|
||||
fixtureDir := filepath.Clean("testdata/load-module-package-escape")
|
||||
dir, done := tempChdir(t, fixtureDir)
|
||||
@ -135,7 +161,10 @@ func TestModuleInstaller_packageEscapeError(t *testing.T) {
|
||||
hooks := &testInstallHooks{}
|
||||
|
||||
modulesDir := filepath.Join(dir, ".terraform/modules")
|
||||
inst := NewModuleInstaller(modulesDir, nil)
|
||||
|
||||
loader, close := configload.NewLoaderForTests(t)
|
||||
defer close()
|
||||
inst := NewModuleInstaller(modulesDir, loader, nil)
|
||||
_, diags := inst.InstallModules(context.Background(), ".", false, hooks)
|
||||
|
||||
if !diags.HasErrors() {
|
||||
@ -170,7 +199,10 @@ func TestModuleInstaller_explicitPackageBoundary(t *testing.T) {
|
||||
hooks := &testInstallHooks{}
|
||||
|
||||
modulesDir := filepath.Join(dir, ".terraform/modules")
|
||||
inst := NewModuleInstaller(modulesDir, nil)
|
||||
|
||||
loader, close := configload.NewLoaderForTests(t)
|
||||
defer close()
|
||||
inst := NewModuleInstaller(modulesDir, loader, nil)
|
||||
_, diags := inst.InstallModules(context.Background(), ".", false, hooks)
|
||||
|
||||
if diags.HasErrors() {
|
||||
@ -190,7 +222,10 @@ func TestModuleInstaller_ExactMatchPrerelease(t *testing.T) {
|
||||
hooks := &testInstallHooks{}
|
||||
|
||||
modulesDir := filepath.Join(dir, ".terraform/modules")
|
||||
inst := NewModuleInstaller(modulesDir, registry.NewClient(nil, nil))
|
||||
|
||||
loader, close := configload.NewLoaderForTests(t)
|
||||
defer close()
|
||||
inst := NewModuleInstaller(modulesDir, loader, registry.NewClient(nil, nil))
|
||||
cfg, diags := inst.InstallModules(context.Background(), ".", false, hooks)
|
||||
|
||||
if diags.HasErrors() {
|
||||
@ -214,7 +249,10 @@ func TestModuleInstaller_PartialMatchPrerelease(t *testing.T) {
|
||||
hooks := &testInstallHooks{}
|
||||
|
||||
modulesDir := filepath.Join(dir, ".terraform/modules")
|
||||
inst := NewModuleInstaller(modulesDir, registry.NewClient(nil, nil))
|
||||
|
||||
loader, close := configload.NewLoaderForTests(t)
|
||||
defer close()
|
||||
inst := NewModuleInstaller(modulesDir, loader, registry.NewClient(nil, nil))
|
||||
cfg, diags := inst.InstallModules(context.Background(), ".", false, hooks)
|
||||
|
||||
if diags.HasErrors() {
|
||||
@ -234,7 +272,10 @@ func TestModuleInstaller_invalid_version_constraint_error(t *testing.T) {
|
||||
hooks := &testInstallHooks{}
|
||||
|
||||
modulesDir := filepath.Join(dir, ".terraform/modules")
|
||||
inst := NewModuleInstaller(modulesDir, nil)
|
||||
|
||||
loader, close := configload.NewLoaderForTests(t)
|
||||
defer close()
|
||||
inst := NewModuleInstaller(modulesDir, loader, nil)
|
||||
_, diags := inst.InstallModules(context.Background(), ".", false, hooks)
|
||||
|
||||
if !diags.HasErrors() {
|
||||
@ -257,7 +298,10 @@ func TestModuleInstaller_invalidVersionConstraintGetter(t *testing.T) {
|
||||
hooks := &testInstallHooks{}
|
||||
|
||||
modulesDir := filepath.Join(dir, ".terraform/modules")
|
||||
inst := NewModuleInstaller(modulesDir, nil)
|
||||
|
||||
loader, close := configload.NewLoaderForTests(t)
|
||||
defer close()
|
||||
inst := NewModuleInstaller(modulesDir, loader, nil)
|
||||
_, diags := inst.InstallModules(context.Background(), ".", false, hooks)
|
||||
|
||||
if !diags.HasErrors() {
|
||||
@ -280,7 +324,10 @@ func TestModuleInstaller_invalidVersionConstraintLocal(t *testing.T) {
|
||||
hooks := &testInstallHooks{}
|
||||
|
||||
modulesDir := filepath.Join(dir, ".terraform/modules")
|
||||
inst := NewModuleInstaller(modulesDir, nil)
|
||||
|
||||
loader, close := configload.NewLoaderForTests(t)
|
||||
defer close()
|
||||
inst := NewModuleInstaller(modulesDir, loader, nil)
|
||||
_, diags := inst.InstallModules(context.Background(), ".", false, hooks)
|
||||
|
||||
if !diags.HasErrors() {
|
||||
@ -303,7 +350,10 @@ func TestModuleInstaller_symlink(t *testing.T) {
|
||||
hooks := &testInstallHooks{}
|
||||
|
||||
modulesDir := filepath.Join(dir, ".terraform/modules")
|
||||
inst := NewModuleInstaller(modulesDir, nil)
|
||||
|
||||
loader, close := configload.NewLoaderForTests(t)
|
||||
defer close()
|
||||
inst := NewModuleInstaller(modulesDir, loader, nil)
|
||||
_, diags := inst.InstallModules(context.Background(), ".", false, hooks)
|
||||
assertNoDiagnostics(t, diags)
|
||||
|
||||
@ -376,7 +426,10 @@ func TestLoaderInstallModules_registry(t *testing.T) {
|
||||
|
||||
hooks := &testInstallHooks{}
|
||||
modulesDir := filepath.Join(dir, ".terraform/modules")
|
||||
inst := NewModuleInstaller(modulesDir, registry.NewClient(nil, nil))
|
||||
|
||||
loader, close := configload.NewLoaderForTests(t)
|
||||
defer close()
|
||||
inst := NewModuleInstaller(modulesDir, loader, registry.NewClient(nil, nil))
|
||||
_, diags := inst.InstallModules(context.Background(), dir, false, hooks)
|
||||
assertNoDiagnostics(t, diags)
|
||||
|
||||
@ -482,7 +535,7 @@ func TestLoaderInstallModules_registry(t *testing.T) {
|
||||
t.Errorf("module download url cache was not populated\ngot: %s", spew.Sdump(inst.registryPackageSources))
|
||||
}
|
||||
|
||||
loader, err := configload.NewLoader(&configload.Config{
|
||||
loader, err = configload.NewLoader(&configload.Config{
|
||||
ModulesDir: modulesDir,
|
||||
})
|
||||
if err != nil {
|
||||
@ -536,7 +589,10 @@ func TestLoaderInstallModules_goGetter(t *testing.T) {
|
||||
|
||||
hooks := &testInstallHooks{}
|
||||
modulesDir := filepath.Join(dir, ".terraform/modules")
|
||||
inst := NewModuleInstaller(modulesDir, registry.NewClient(nil, nil))
|
||||
|
||||
loader, close := configload.NewLoaderForTests(t)
|
||||
defer close()
|
||||
inst := NewModuleInstaller(modulesDir, loader, registry.NewClient(nil, nil))
|
||||
_, diags := inst.InstallModules(context.Background(), dir, false, hooks)
|
||||
assertNoDiagnostics(t, diags)
|
||||
|
||||
@ -609,7 +665,7 @@ func TestLoaderInstallModules_goGetter(t *testing.T) {
|
||||
t.Fatalf("wrong installer calls\n%s", diff)
|
||||
}
|
||||
|
||||
loader, err := configload.NewLoader(&configload.Config{
|
||||
loader, err = configload.NewLoader(&configload.Config{
|
||||
ModulesDir: modulesDir,
|
||||
})
|
||||
if err != nil {
|
||||
|
3
internal/initwd/testdata/empty-module-name/child/main.tf
vendored
Normal file
3
internal/initwd/testdata/empty-module-name/child/main.tf
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
output "boop" {
|
||||
value = "beep"
|
||||
}
|
3
internal/initwd/testdata/empty-module-name/main.tf
vendored
Normal file
3
internal/initwd/testdata/empty-module-name/main.tf
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
module "" {
|
||||
source = "./child"
|
||||
}
|
@ -34,7 +34,7 @@ func LoadConfigForTests(t *testing.T, rootDir string) (*configs.Config, *configl
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
loader, cleanup := configload.NewLoaderForTests(t)
|
||||
inst := NewModuleInstaller(loader.ModulesDir(), registry.NewClient(nil, nil))
|
||||
inst := NewModuleInstaller(loader.ModulesDir(), loader, registry.NewClient(nil, nil))
|
||||
|
||||
_, moreDiags := inst.InstallModules(context.Background(), rootDir, true, ModuleInstallHooksImpl{})
|
||||
diags = diags.Append(moreDiags)
|
||||
|
@ -20,7 +20,7 @@ func testAnalyzer(t *testing.T, fixtureName string) *Analyzer {
|
||||
loader, cleanup := configload.NewLoaderForTests(t)
|
||||
defer cleanup()
|
||||
|
||||
inst := initwd.NewModuleInstaller(loader.ModulesDir(), registry.NewClient(nil, nil))
|
||||
inst := initwd.NewModuleInstaller(loader.ModulesDir(), loader, registry.NewClient(nil, nil))
|
||||
_, instDiags := inst.InstallModules(context.Background(), configDir, true, initwd.ModuleInstallHooksImpl{})
|
||||
if instDiags.HasErrors() {
|
||||
t.Fatalf("unexpected module installation errors: %s", instDiags.Err().Error())
|
||||
|
@ -1,62 +0,0 @@
|
||||
package terraform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/configs"
|
||||
|
||||
tfversion "github.com/hashicorp/terraform/version"
|
||||
)
|
||||
|
||||
// CheckCoreVersionRequirements visits each of the modules in the given
|
||||
// configuration tree and verifies that any given Core version constraints
|
||||
// match with the version of Terraform Core that is being used.
|
||||
//
|
||||
// The returned diagnostics will contain errors if any constraints do not match.
|
||||
// The returned diagnostics might also return warnings, which should be
|
||||
// displayed to the user.
|
||||
func CheckCoreVersionRequirements(config *configs.Config) tfdiags.Diagnostics {
|
||||
if config == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var diags tfdiags.Diagnostics
|
||||
module := config.Module
|
||||
|
||||
for _, constraint := range module.CoreVersionConstraints {
|
||||
if !constraint.Required.Check(tfversion.SemVer) {
|
||||
switch {
|
||||
case len(config.Path) == 0:
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unsupported Terraform Core version",
|
||||
Detail: fmt.Sprintf(
|
||||
"This configuration does not support Terraform version %s. To proceed, either choose another supported Terraform version or update this version constraint. Version constraints are normally set for good reason, so updating the constraint may lead to other errors or unexpected behavior.",
|
||||
tfversion.String(),
|
||||
),
|
||||
Subject: constraint.DeclRange.Ptr(),
|
||||
})
|
||||
default:
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unsupported Terraform Core version",
|
||||
Detail: fmt.Sprintf(
|
||||
"Module %s (from %s) does not support Terraform version %s. To proceed, either choose another supported Terraform version or update this version constraint. Version constraints are normally set for good reason, so updating the constraint may lead to other errors or unexpected behavior.",
|
||||
config.Path, config.SourceAddr, tfversion.String(),
|
||||
),
|
||||
Subject: constraint.DeclRange.Ptr(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, c := range config.Children {
|
||||
childDiags := CheckCoreVersionRequirements(c)
|
||||
diags = diags.Append(childDiags)
|
||||
}
|
||||
|
||||
return diags
|
||||
}
|
@ -534,7 +534,7 @@ func loadRefactoringFixture(t *testing.T, dir string) (*configs.Config, instance
|
||||
loader, cleanup := configload.NewLoaderForTests(t)
|
||||
defer cleanup()
|
||||
|
||||
inst := initwd.NewModuleInstaller(loader.ModulesDir(), registry.NewClient(nil, nil))
|
||||
inst := initwd.NewModuleInstaller(loader.ModulesDir(), loader, registry.NewClient(nil, nil))
|
||||
_, instDiags := inst.InstallModules(context.Background(), dir, true, initwd.ModuleInstallHooksImpl{})
|
||||
if instDiags.HasErrors() {
|
||||
t.Fatal(instDiags.Err())
|
||||
|
@ -38,14 +38,12 @@ var (
|
||||
func TestNewContextRequiredVersion(t *testing.T) {
|
||||
cases := []struct {
|
||||
Name string
|
||||
Module string
|
||||
Version string
|
||||
Value string
|
||||
Err bool
|
||||
}{
|
||||
{
|
||||
"no requirement",
|
||||
"",
|
||||
"0.1.0",
|
||||
"",
|
||||
false,
|
||||
@ -53,7 +51,6 @@ func TestNewContextRequiredVersion(t *testing.T) {
|
||||
|
||||
{
|
||||
"doesn't match",
|
||||
"",
|
||||
"0.1.0",
|
||||
"> 0.6.0",
|
||||
true,
|
||||
@ -61,7 +58,6 @@ func TestNewContextRequiredVersion(t *testing.T) {
|
||||
|
||||
{
|
||||
"matches",
|
||||
"",
|
||||
"0.7.0",
|
||||
"> 0.6.0",
|
||||
false,
|
||||
@ -69,7 +65,6 @@ func TestNewContextRequiredVersion(t *testing.T) {
|
||||
|
||||
{
|
||||
"prerelease doesn't match with inequality",
|
||||
"",
|
||||
"0.8.0",
|
||||
"> 0.7.0-beta",
|
||||
true,
|
||||
@ -77,27 +72,10 @@ func TestNewContextRequiredVersion(t *testing.T) {
|
||||
|
||||
{
|
||||
"prerelease doesn't match with equality",
|
||||
"",
|
||||
"0.7.0",
|
||||
"0.7.0-beta",
|
||||
true,
|
||||
},
|
||||
|
||||
{
|
||||
"module matches",
|
||||
"context-required-version-module",
|
||||
"0.5.0",
|
||||
"",
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"module doesn't match",
|
||||
"context-required-version-module",
|
||||
"0.4.0",
|
||||
"",
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
@ -107,11 +85,7 @@ func TestNewContextRequiredVersion(t *testing.T) {
|
||||
tfversion.SemVer = version.Must(version.NewVersion(tc.Version))
|
||||
defer func() { tfversion.SemVer = old }()
|
||||
|
||||
name := "context-required-version"
|
||||
if tc.Module != "" {
|
||||
name = tc.Module
|
||||
}
|
||||
mod := testModule(t, name)
|
||||
mod := testModule(t, "context-required-version")
|
||||
if tc.Value != "" {
|
||||
constraint, err := version.NewConstraint(tc.Value)
|
||||
if err != nil {
|
||||
@ -134,6 +108,65 @@ func TestNewContextRequiredVersion(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewContextRequiredVersion_child(t *testing.T) {
|
||||
mod := testModuleInline(t, map[string]string{
|
||||
"main.tf": `
|
||||
module "child" {
|
||||
source = "./child"
|
||||
}
|
||||
`,
|
||||
"child/main.tf": `
|
||||
terraform {}
|
||||
`,
|
||||
})
|
||||
|
||||
cases := map[string]struct {
|
||||
Version string
|
||||
Constraint string
|
||||
Err bool
|
||||
}{
|
||||
"matches": {
|
||||
"0.5.0",
|
||||
">= 0.5.0",
|
||||
false,
|
||||
},
|
||||
"doesn't match": {
|
||||
"0.4.0",
|
||||
">= 0.5.0",
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
// Reset the version for the tests
|
||||
old := tfversion.SemVer
|
||||
tfversion.SemVer = version.Must(version.NewVersion(tc.Version))
|
||||
defer func() { tfversion.SemVer = old }()
|
||||
|
||||
if tc.Constraint != "" {
|
||||
constraint, err := version.NewConstraint(tc.Constraint)
|
||||
if err != nil {
|
||||
t.Fatalf("can't parse %q as version constraint", tc.Constraint)
|
||||
}
|
||||
child := mod.Children["child"]
|
||||
child.Module.CoreVersionConstraints = append(child.Module.CoreVersionConstraints, configs.VersionConstraint{
|
||||
Required: constraint,
|
||||
})
|
||||
}
|
||||
c, diags := NewContext(&ContextOpts{})
|
||||
if diags.HasErrors() {
|
||||
t.Fatalf("unexpected NewContext errors: %s", diags.Err())
|
||||
}
|
||||
|
||||
diags = c.Validate(mod)
|
||||
if diags.HasErrors() != tc.Err {
|
||||
t.Fatalf("err: %s", diags.Err())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext_missingPlugins(t *testing.T) {
|
||||
ctx, diags := NewContext(&ContextOpts{})
|
||||
assertNoDiagnostics(t, diags)
|
||||
|
@ -63,7 +63,7 @@ func testModuleWithSnapshot(t *testing.T, name string) (*configs.Config, *config
|
||||
// Test modules usually do not refer to remote sources, and for local
|
||||
// sources only this ultimately just records all of the module paths
|
||||
// in a JSON file so that we can load them below.
|
||||
inst := initwd.NewModuleInstaller(loader.ModulesDir(), registry.NewClient(nil, nil))
|
||||
inst := initwd.NewModuleInstaller(loader.ModulesDir(), loader, registry.NewClient(nil, nil))
|
||||
_, instDiags := inst.InstallModules(context.Background(), dir, true, initwd.ModuleInstallHooksImpl{})
|
||||
if instDiags.HasErrors() {
|
||||
t.Fatal(instDiags.Err())
|
||||
@ -120,7 +120,7 @@ func testModuleInline(t *testing.T, sources map[string]string) *configs.Config {
|
||||
// Test modules usually do not refer to remote sources, and for local
|
||||
// sources only this ultimately just records all of the module paths
|
||||
// in a JSON file so that we can load them below.
|
||||
inst := initwd.NewModuleInstaller(loader.ModulesDir(), registry.NewClient(nil, nil))
|
||||
inst := initwd.NewModuleInstaller(loader.ModulesDir(), loader, registry.NewClient(nil, nil))
|
||||
_, instDiags := inst.InstallModules(context.Background(), cfgPath, true, initwd.ModuleInstallHooksImpl{})
|
||||
if instDiags.HasErrors() {
|
||||
t.Fatal(instDiags.Err())
|
||||
|
@ -1,3 +0,0 @@
|
||||
terraform {
|
||||
required_version = ">= 0.5.0"
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
module "child" {
|
||||
source = "./child"
|
||||
}
|
@ -1,14 +1,9 @@
|
||||
package terraform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/configs"
|
||||
|
||||
tfversion "github.com/hashicorp/terraform/version"
|
||||
)
|
||||
|
||||
// CheckCoreVersionRequirements visits each of the modules in the given
|
||||
@ -24,62 +19,7 @@ func CheckCoreVersionRequirements(config *configs.Config) tfdiags.Diagnostics {
|
||||
}
|
||||
|
||||
var diags tfdiags.Diagnostics
|
||||
module := config.Module
|
||||
|
||||
for _, constraint := range module.CoreVersionConstraints {
|
||||
// Before checking if the constraints are met, check that we are not using any prerelease fields as these
|
||||
// are not currently supported.
|
||||
var prereleaseDiags tfdiags.Diagnostics
|
||||
for _, required := range constraint.Required {
|
||||
if required.Prerelease() {
|
||||
prereleaseDiags = prereleaseDiags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid required_version constraint",
|
||||
Detail: fmt.Sprintf(
|
||||
"Prerelease version constraints are not supported: %s. Remove the prerelease information from the constraint. Prerelease versions of terraform will match constraints using their version core only.",
|
||||
required.String()),
|
||||
Subject: constraint.DeclRange.Ptr(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if len(prereleaseDiags) > 0 {
|
||||
// There were some prerelease fields in the constraints. Don't check the constraints as they will
|
||||
// fail, and populate the diagnostics for these constraints with the prerelease diagnostics.
|
||||
diags = diags.Append(prereleaseDiags)
|
||||
continue
|
||||
}
|
||||
|
||||
if !constraint.Required.Check(tfversion.SemVer) {
|
||||
switch {
|
||||
case len(config.Path) == 0:
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unsupported Terraform Core version",
|
||||
Detail: fmt.Sprintf(
|
||||
"This configuration does not support Terraform version %s. To proceed, either choose another supported Terraform version or update this version constraint. Version constraints are normally set for good reason, so updating the constraint may lead to other errors or unexpected behavior.",
|
||||
tfversion.String(),
|
||||
),
|
||||
Subject: constraint.DeclRange.Ptr(),
|
||||
})
|
||||
default:
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unsupported Terraform Core version",
|
||||
Detail: fmt.Sprintf(
|
||||
"Module %s (from %s) does not support Terraform version %s. To proceed, either choose another supported Terraform version or update this version constraint. Version constraints are normally set for good reason, so updating the constraint may lead to other errors or unexpected behavior.",
|
||||
config.Path, config.SourceAddr, tfversion.String(),
|
||||
),
|
||||
Subject: constraint.DeclRange.Ptr(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, c := range config.Children {
|
||||
childDiags := CheckCoreVersionRequirements(c)
|
||||
diags = diags.Append(childDiags)
|
||||
}
|
||||
diags = diags.Append(config.CheckCoreVersionRequirements())
|
||||
|
||||
return diags
|
||||
}
|
||||
|
@ -2,15 +2,14 @@ package version
|
||||
|
||||
import "runtime/debug"
|
||||
|
||||
// See the docs for InterestingDependencyVersions to understand what
|
||||
// "interesting" is intended to mean here. We should keep this set relatively
|
||||
// small to avoid bloating the logs too much.
|
||||
// See the docs for InterestingDependencies to understand what "interesting" is
|
||||
// intended to mean here. We should keep this set relatively small to avoid
|
||||
// bloating the logs too much.
|
||||
var interestingDependencies = map[string]struct{}{
|
||||
"github.com/hashicorp/hcl/v2": {},
|
||||
"github.com/zclconf/go-cty": {},
|
||||
"github.com/hashicorp/go-tfe": {},
|
||||
"github.com/hashicorp/terraform-config-inspect": {},
|
||||
"github.com/hashicorp/terraform-svchost": {},
|
||||
"github.com/hashicorp/hcl/v2": {},
|
||||
"github.com/zclconf/go-cty": {},
|
||||
"github.com/hashicorp/go-tfe": {},
|
||||
"github.com/hashicorp/terraform-svchost": {},
|
||||
}
|
||||
|
||||
// InterestingDependencies returns the compiled-in module version info for
|
||||
|
Loading…
Reference in New Issue
Block a user