Skip to content

Commit

Permalink
1. initial changes for registry module support
Browse files Browse the repository at this point in the history
2. fix issue of remote module containing local modules
  • Loading branch information
patilpankaj212 committed Jan 22, 2021
1 parent 51b686e commit 78fd1d2
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 1 deletion.
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1498,6 +1498,12 @@ sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbL
sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU=
sigs.k8s.io/kustomize/api v0.7.2 h1:ItTD/2XaKO8CosOMFZdaGFdUGTCHdQriW7zQ7AR98rs=
sigs.k8s.io/kustomize/api v0.7.2/go.mod h1:50/vLATrjhRmMr3spZsI1GcpoZJ8IARy9QstPbA9lGE=
sigs.k8s.io/kustomize/api v0.7.1 h1:/cjDi4Pk/hqRSeCCj/Xum66rYrEtc7osM2/O+lvYKkM=
sigs.k8s.io/kustomize/api v0.7.1/go.mod h1:XOt24UrCkv0x63eT5JVaph4Kqf5EVU2UBAXo6SPBaAY=
sigs.k8s.io/kustomize/api v0.7.2 h1:ItTD/2XaKO8CosOMFZdaGFdUGTCHdQriW7zQ7AR98rs=
sigs.k8s.io/kustomize/api v0.7.2/go.mod h1:50/vLATrjhRmMr3spZsI1GcpoZJ8IARy9QstPbA9lGE=
sigs.k8s.io/kustomize/kyaml v0.10.5 h1:PbJcsZsEM7O3hHtUWTR+4WkHVbQRW9crSy75or1gRbI=
sigs.k8s.io/kustomize/kyaml v0.10.5/go.mod h1:P6Oy/ah/GZMKzJMIJA2a3/bc8YrBkuL5kJji13PSIzY=
sigs.k8s.io/kustomize/kyaml v0.10.6 h1:xUJxc/k8JoWqHUahaB8DTqY0KwEPxTbTGStvW8TOcDc=
sigs.k8s.io/kustomize/kyaml v0.10.6/go.mod h1:K9yg1k/HB/6xNOf5VH3LhTo1DK9/5ykSZO5uIv+Y/1k=
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e h1:4Z09Hglb792X0kfOBBJUPFEyvVfQWrYT/l8h5EKA6JQ=
Expand Down
7 changes: 7 additions & 0 deletions pkg/cli/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ package cli

import (
"flag"
"io/ioutil"
"log"
"os"

"github.com/accurics/terrascan/pkg/config"
Expand Down Expand Up @@ -59,6 +61,11 @@ func Execute() {
}
}

// disable terraform logs when TF_LOG env variable is not set
if os.Getenv("TF_LOG") == "" {
log.SetOutput(ioutil.Discard)
}

if err := rootCmd.Execute(); err != nil {
os.Exit(1)
}
Expand Down
35 changes: 34 additions & 1 deletion pkg/iac-providers/terraform/commons/load-dir.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ import (
"fmt"
"os"
"path/filepath"
"strings"

"github.com/accurics/terrascan/pkg/iac-providers/output"
"github.com/accurics/terrascan/pkg/utils"
version "github.com/hashicorp/go-version"
"github.com/hashicorp/hcl/v2"
hclConfigs "github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/registry/regsrc"
"github.com/spf13/afero"
"go.uber.org/zap"
)
Expand Down Expand Up @@ -53,6 +55,9 @@ type ModuleConfig struct {
// resources present in rootDir and descendant modules
func LoadIacDir(absRootDir string) (allResourcesConfig output.AllResourceConfigs, err error) {

// map to hold the download paths of remote modules
remoteModPaths := make(map[string]string)

// create a new config parser
parser := hclConfigs.NewParser(afero.NewOsFs())

Expand Down Expand Up @@ -92,8 +97,36 @@ func LoadIacDir(absRootDir string) (allResourcesConfig output.AllResourceConfigs
for p := req.Parent; p != nil; p = p.Parent {
pathToModule = filepath.Join(p.SourceAddr, pathToModule)
}
pathToModule = filepath.Join(absRootDir, pathToModule)

// check if pathToModule consists of a remote module downloaded
// if yes, then update the module path, with the remote module
// download path
keyFound := false
for remoteSourceAddr, downloadPath := range remoteModPaths {
if strings.Contains(pathToModule, remoteSourceAddr) {
pathToModule = strings.Replace(pathToModule, remoteSourceAddr, "", 1)
pathToModule = downloadPath + pathToModule
keyFound = true
break
}
}
if !keyFound {
pathToModule = filepath.Join(absRootDir, pathToModule)
}
zap.S().Debugf("processing local module %q", pathToModule)
} else if isRegistrySourceAddr(req.SourceAddr) {
// regsrc.ParseModuleSource func returns a terraform registry module source
// error check is not required as the source address is already validated above
module, _ := regsrc.ParseModuleSource(req.SourceAddr)

tempDir := filepath.Join(os.TempDir(), utils.GenRandomString(6))
pathToModule, err = r.DownloadRemoteModule(req.VersionConstraint, tempDir, module)
if err != nil {
zap.S().Errorf("failed to download remote module %q. error: '%v'", req.SourceAddr, err)
} else {
// add the entry of remote module's source address to the map
remoteModPaths[req.SourceAddr] = pathToModule
}
} else {
// temp dir to download the remote repo
tempDir := filepath.Join(os.TempDir(), utils.GenRandomString(6))
Expand Down
11 changes: 11 additions & 0 deletions pkg/iac-providers/terraform/commons/module-download.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import (

"github.com/accurics/terrascan/pkg/downloader"
"go.uber.org/zap"

"github.com/hashicorp/terraform/registry/regsrc"
)

var localSourcePrefixes = []string{
Expand All @@ -41,6 +43,15 @@ func isLocalSourceAddr(addr string) bool {
return false
}

// isRegistrySourceAddr will validate if the source address is a valid registry
// module or not.
// a valid source address is of the form <HOSTNAME>/NAMESPACE>/<NAME>/<PROVIDER>
// regsrc.ParseModuleSource func returns a terraform registry module source.
func isRegistrySourceAddr(addr string) bool {
_, err := regsrc.ParseModuleSource(addr)
return err == nil
}

// RemoteModuleInstaller helps in downloading remote modules, it also maintains a
// cache of all the installed modules and their respective resolved addresses
// (URL)
Expand Down
137 changes: 137 additions & 0 deletions pkg/iac-providers/terraform/commons/remote-module-download.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/*
Copyright (C) 2020 Accurics, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package commons

import (
"fmt"
"path/filepath"

version "github.com/hashicorp/go-version"
hclConfigs "github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/registry"
"github.com/hashicorp/terraform/registry/regsrc"
"go.uber.org/zap"
)

// DownloadRemoteModule will download remote modules from public and private terraform registries
// this function takes similar approach taken by terraform init for downloading terraform registry modules
func (r *RemoteModuleInstaller) DownloadRemoteModule(requiredVersion hclConfigs.VersionConstraint, destPath string, module *regsrc.Module) (string, error) {
// Terraform doesn't allow the hostname to contain Punycode
// module.SvcHost returns an error for such case
_, err := module.SvcHost()
if err != nil {
zap.S().Errorf("hostname for the module %s is invalid", module.String())
return "", err
}

// get terraform registry client.
// terraform registry client provides methods for querying the terraform module registry
regClient := registry.NewClient(nil, nil)

// get all the available module versions from the terraform registry
moduleVersions, err := regClient.ModuleVersions(module)
if err != nil {
if registry.IsModuleNotFound(err) {
zap.S().Errorf("module: %s, not be found at registry: %s", module.String(), module.Host().Display())
} else {
zap.S().Errorf("error while fetching available modules for module: %s, at registry: %s", module.String(), module.Host().Display())
}
return "", err
}

// terraform init command pulls all the available versions of a module,
// and downloads the latest non pre-release (unless a pre-release version was
// specified in tf file) version, if a version constraint is not provided in the tf file.
// we are following what terraform does
allModules := moduleVersions.Modules[0]

var latestMatch *version.Version
var latestVersion *version.Version
var versionToDownload *version.Version
for _, moduleVersion := range allModules.Versions {
currentVersion, err := version.NewVersion(moduleVersion.Version)
if err != nil {
// if error is received for a version, then skip the current version
zap.S().Errorf("invalid version: %s, for module: %s, at registry: %s", moduleVersion.Version, module.String(), module.Host().Display())
continue
}

if requiredVersion.Required == nil {
// skip the pre release version
if currentVersion.Prerelease() != "" {
continue
}

// update the latest version
latestVersion = getGreaterVersion(latestVersion, currentVersion)
} else {
// skip the pre-release version, unless specified in the tf file
if currentVersion.Prerelease() != "" && requiredVersion.Required.String() != currentVersion.String() {
continue
}

// update the latest version
latestVersion = getGreaterVersion(latestVersion, currentVersion)

// update latest match
if requiredVersion.Required.Check(currentVersion) {
latestMatch = getGreaterVersion(latestMatch, currentVersion)
}
}
}

if latestVersion == nil {
return "", fmt.Errorf("no versions for module: %s, found at registry: %s", module.String(), module.Host().Display())
}

if requiredVersion.Required != nil && latestMatch == nil {
return "", fmt.Errorf("no versions matching: %s, for module: %s, found at registry: %s, latest version found: %s", requiredVersion.Required.String(), module.String(), module.Host().Display(), latestVersion.String())
}

versionToDownload = latestVersion
if latestMatch != nil {
versionToDownload = latestMatch
}

// get the source location for the matched version
sourceLocation, err := regClient.ModuleLocation(module, versionToDownload.String())
if err != nil {
zap.S().Errorf("error while getting the source location for module: %s, at registry: %s", module.String(), module.Host().Display())
return "", err
}

downloadLocation, err := r.DownloadModule(sourceLocation, destPath)
if err != nil {
zap.S().Errorf("error while downloading module: %s, with source location: %s", module.String(), sourceLocation)
return "", nil
}

if module.RawSubmodule != "" {
// Append the user's requested subdirectory to any subdirectory that
// was implied by any of the nested layers we expanded within go-getter.
downloadLocation = filepath.Join(downloadLocation, module.RawSubmodule)
}

return downloadLocation, nil
}

func getGreaterVersion(latestVersion *version.Version, currentVersion *version.Version) *version.Version {
if latestVersion == nil || currentVersion.GreaterThan(latestVersion) {
latestVersion = currentVersion
}
return latestVersion
}

0 comments on commit 78fd1d2

Please sign in to comment.