Skip to content

Commit

Permalink
Add --tf-download-url configuration option
Browse files Browse the repository at this point in the history
  • Loading branch information
cullenmcdermott committed Sep 24, 2019
1 parent e4dc33d commit c864731
Show file tree
Hide file tree
Showing 8 changed files with 61 additions and 23 deletions.
9 changes: 9 additions & 0 deletions cmd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ const (
SlackTokenFlag = "slack-token"
SSLCertFileFlag = "ssl-cert-file"
SSLKeyFileFlag = "ssl-key-file"
TFDownloadURLFlag = "tf-download-url"
TFEHostnameFlag = "tfe-hostname"
TFETokenFlag = "tfe-token"
WriteGitCredsFlag = "write-git-creds"
Expand All @@ -81,6 +82,7 @@ const (
DefaultGitlabHostname = "gitlab.com"
DefaultLogLevel = "info"
DefaultPort = 4141
DefaultTFDownloadURL = "https://releases.hashicorp.com"
DefaultTFEHostname = "app.terraform.io"
)

Expand Down Expand Up @@ -179,6 +181,10 @@ var stringFlags = map[string]stringFlag{
SSLKeyFileFlag: {
description: fmt.Sprintf("File containing x509 private key matching --%s.", SSLCertFileFlag),
},
TFDownloadURLFlag: {
description: "The URL to download Terraform from.",
defaultValue: DefaultTFDownloadURL,
},
TFEHostnameFlag: {
description: "Hostname of your Terraform Enterprise installation. If using Terraform Cloud no need to set.",
defaultValue: DefaultTFEHostname,
Expand Down Expand Up @@ -431,6 +437,9 @@ func (s *ServerCmd) setDefaults(c *server.UserConfig) {
if c.Port == 0 {
c.Port = DefaultPort
}
if c.TFDownloadURL == "" {
c.TFDownloadURL = DefaultTFDownloadURL
}
if c.TFEHostname == "" {
c.TFEHostname = DefaultTFEHostname
}
Expand Down
14 changes: 14 additions & 0 deletions cmd/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@ func TestExecute_Defaults(t *testing.T) {
Equals(t, "", passedConfig.SlackToken)
Equals(t, "", passedConfig.SSLCertFile)
Equals(t, "", passedConfig.SSLKeyFile)
Equals(t, "https://releases.hashicorp.com", passedConfig.TFDownloadURL)
Equals(t, "app.terraform.io", passedConfig.TFEHostname)
Equals(t, "", passedConfig.TFEToken)
Equals(t, false, passedConfig.WriteGitCreds)
Expand Down Expand Up @@ -468,6 +469,7 @@ func TestExecute_Flags(t *testing.T) {
cmd.SlackTokenFlag: "slack-token",
cmd.SSLCertFileFlag: "cert-file",
cmd.SSLKeyFileFlag: "key-file",
cmd.TFDownloadURLFlag: "https://my-hostname.com",
cmd.TFEHostnameFlag: "my-hostname",
cmd.TFETokenFlag: "my-token",
cmd.WriteGitCredsFlag: true,
Expand Down Expand Up @@ -503,6 +505,7 @@ func TestExecute_Flags(t *testing.T) {
Equals(t, "slack-token", passedConfig.SlackToken)
Equals(t, "cert-file", passedConfig.SSLCertFile)
Equals(t, "key-file", passedConfig.SSLKeyFile)
Equals(t, "https://my-hostname.com", passedConfig.TFDownloadURL)
Equals(t, "my-hostname", passedConfig.TFEHostname)
Equals(t, "my-token", passedConfig.TFEToken)
Equals(t, true, passedConfig.WriteGitCreds)
Expand Down Expand Up @@ -539,6 +542,7 @@ require-mergeable: true
slack-token: slack-token
ssl-cert-file: cert-file
ssl-key-file: key-file
tf-download-url: https://my-hostname.com
tfe-hostname: my-hostname
tfe-token: my-token
write-git-creds: true
Expand Down Expand Up @@ -578,6 +582,7 @@ write-git-creds: true
Equals(t, "slack-token", passedConfig.SlackToken)
Equals(t, "cert-file", passedConfig.SSLCertFile)
Equals(t, "key-file", passedConfig.SSLKeyFile)
Equals(t, "https://my-hostname.com", passedConfig.TFDownloadURL)
Equals(t, "my-hostname", passedConfig.TFEHostname)
Equals(t, "my-token", passedConfig.TFEToken)
Equals(t, true, passedConfig.WriteGitCreds)
Expand Down Expand Up @@ -613,6 +618,7 @@ require-approval: true
slack-token: slack-token
ssl-cert-file: cert-file
ssl-key-file: key-file
tf-download-url: https://my-hostname.com
tfe-hostname: my-hostname
tfe-token: my-token
write-git-creds: true
Expand Down Expand Up @@ -649,6 +655,7 @@ write-git-creds: true
"SLACK_TOKEN": "override-slack-token",
"SSL_CERT_FILE": "override-cert-file",
"SSL_KEY_FILE": "override-key-file",
"TF_DOWNLOAD_URL": "https://override-my-hostname.com",
"TFE_HOSTNAME": "override-my-hostname",
"TFE_TOKEN": "override-my-token",
"WRITE_GIT_CREDS": "false",
Expand Down Expand Up @@ -688,6 +695,7 @@ write-git-creds: true
Equals(t, "override-slack-token", passedConfig.SlackToken)
Equals(t, "override-cert-file", passedConfig.SSLCertFile)
Equals(t, "override-key-file", passedConfig.SSLKeyFile)
Equals(t, "https://override-my-hostname.com", passedConfig.TFDownloadURL)
Equals(t, "override-my-hostname", passedConfig.TFEHostname)
Equals(t, "override-my-token", passedConfig.TFEToken)
Equals(t, false, passedConfig.WriteGitCreds)
Expand Down Expand Up @@ -724,6 +732,7 @@ require-mergeable: true
slack-token: slack-token
ssl-cert-file: cert-file
ssl-key-file: key-file
tf-download-url: https://my-hostname.com
tfe-hostname: my-hostname
tfe-token: my-token
write-git-creds: true
Expand Down Expand Up @@ -759,6 +768,7 @@ write-git-creds: true
cmd.SlackTokenFlag: "override-slack-token",
cmd.SSLCertFileFlag: "override-cert-file",
cmd.SSLKeyFileFlag: "override-key-file",
cmd.TFDownloadURLFlag: "https://override-my-hostname.com",
cmd.TFEHostnameFlag: "override-my-hostname",
cmd.TFETokenFlag: "override-my-token",
cmd.WriteGitCredsFlag: false,
Expand Down Expand Up @@ -792,6 +802,7 @@ write-git-creds: true
Equals(t, "override-slack-token", passedConfig.SlackToken)
Equals(t, "override-cert-file", passedConfig.SSLCertFile)
Equals(t, "override-key-file", passedConfig.SSLKeyFile)
Equals(t, "https://override-my-hostname.com", passedConfig.TFDownloadURL)
Equals(t, "override-my-hostname", passedConfig.TFEHostname)
Equals(t, "override-my-token", passedConfig.TFEToken)
Equals(t, false, passedConfig.WriteGitCreds)
Expand Down Expand Up @@ -830,6 +841,7 @@ func TestExecute_FlagEnvVarOverride(t *testing.T) {
"SLACK_TOKEN": "slack-token",
"SSL_CERT_FILE": "cert-file",
"SSL_KEY_FILE": "key-file",
"TF_DOWNLOAD_URL": "https://my-hostname.com",
"TFE_HOSTNAME": "my-hostname",
"TFE_TOKEN": "my-token",
"WRITE_GIT_CREDS": "true",
Expand Down Expand Up @@ -873,6 +885,7 @@ func TestExecute_FlagEnvVarOverride(t *testing.T) {
cmd.SlackTokenFlag: "override-slack-token",
cmd.SSLCertFileFlag: "override-cert-file",
cmd.SSLKeyFileFlag: "override-key-file",
cmd.TFDownloadURLFlag: "https://override-my-hostname.com",
cmd.TFEHostnameFlag: "override-my-hostname",
cmd.TFETokenFlag: "override-my-token",
cmd.WriteGitCredsFlag: false,
Expand Down Expand Up @@ -908,6 +921,7 @@ func TestExecute_FlagEnvVarOverride(t *testing.T) {
Equals(t, "override-slack-token", passedConfig.SlackToken)
Equals(t, "override-cert-file", passedConfig.SSLCertFile)
Equals(t, "override-key-file", passedConfig.SSLKeyFile)
Equals(t, "https://override-my-hostname.com", passedConfig.TFDownloadURL)
Equals(t, "override-my-hostname", passedConfig.TFEHostname)
Equals(t, "override-my-token", passedConfig.TFEToken)
Equals(t, false, passedConfig.WriteGitCreds)
Expand Down
10 changes: 9 additions & 1 deletion runatlantis.io/docs/server-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,15 @@ Values are chosen in this order:
atlantis server --ssl-cert-file="/etc/ssl/private/my-cert.key"
```
File containing x509 private key matching `--ssl-cert-file`.


* ### `--tf-download-url`
```bash
atlantis server --tf-download-url="https://releases.company.com"
```
An alternative URL to download Terraform Versions if they are missing. Useful in an airgapped
environment where releases.hashicorp.com is not available. Directory structure of the custom
endpoint should match that of releases.hashicorp.com.

* ### `--tfe-hostname`
```bash
atlantis server --tfe-hostname="my-terraform-enterprise.company.com"
Expand Down
23 changes: 14 additions & 9 deletions server/events/terraform/terraform_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ type DefaultClient struct {
overrideTF string
// downloader downloads terraform versions.
downloader Downloader
downloadURL string
// versions maps from the string representation of a tf version (ex. 0.11.10)
// to the absolute path of that binary on disk (if it exists).
// Use versionsLock to control access.
Expand All @@ -80,8 +81,6 @@ const (
// binDirName is the name of the directory inside our data dir where
// we download terraform binaries.
binDirName = "bin"
// releasesURL is the base url to download terraform from.
releasesURL = "https://releases.hashicorp.com"
)

// versionRegex extracts the version from `terraform version` output.
Expand All @@ -107,6 +106,7 @@ func NewClient(
tfeHostname string,
defaultVersionStr string,
defaultVersionFlagName string,
tfDownloadURL string,
tfDownloader Downloader) (*DefaultClient, error) {
var finalDefaultVersion *version.Version
var localVersion *version.Version
Expand Down Expand Up @@ -145,7 +145,7 @@ func NewClient(
// Since ensureVersion might end up downloading terraform,
// we call it asynchronously so as to not delay server startup.
versionsLock.Lock()
_, err := ensureVersion(log, tfDownloader, versions, defaultVersion, binDir)
_, err := ensureVersion(log, tfDownloader, versions, defaultVersion, binDir, tfDownloadURL)
versionsLock.Unlock()
if err != nil {
log.Err("could not download terraform %s", defaultVersion.String())
Expand Down Expand Up @@ -176,6 +176,7 @@ func NewClient(
terraformPluginCacheDir: cacheDir,
binDir: binDir,
downloader: tfDownloader,
downloadURL: tfDownloadURL,
versionsLock: &versionsLock,
versions: versions,
}, nil
Expand All @@ -192,6 +193,11 @@ func (c *DefaultClient) TerraformBinDir() string {
return c.binDir
}

//TerraformDownloadURL returns the URL where we download Terraform binaries.
func (c *DefaultClient) TerraformDownloadURL() string {
return c.downloadURL
}

// See Client.EnsureVersion.
func (c *DefaultClient) EnsureVersion(log *logging.SimpleLogger, v *version.Version) error {
if v == nil {
Expand All @@ -200,7 +206,7 @@ func (c *DefaultClient) EnsureVersion(log *logging.SimpleLogger, v *version.Vers

var err error
c.versionsLock.Lock()
_, err = ensureVersion(log, c.downloader, c.versions, v, c.binDir)
_, err = ensureVersion(log, c.downloader, c.versions, v, c.binDir, c.downloadURL)
c.versionsLock.Unlock()
if err != nil {
return err
Expand Down Expand Up @@ -245,7 +251,7 @@ func (c *DefaultClient) prepCmd(log *logging.SimpleLogger, v *version.Version, w
} else {
var err error
c.versionsLock.Lock()
binPath, err = ensureVersion(log, c.downloader, c.versions, v, c.binDir)
binPath, err = ensureVersion(log, c.downloader, c.versions, v, c.binDir, c.downloadURL)
c.versionsLock.Unlock()
if err != nil {
return "", nil, err
Expand Down Expand Up @@ -390,7 +396,7 @@ func MustConstraint(v string) version.Constraints {

// ensureVersion returns the path to a terraform binary of version v.
// It will download this version if we don't have it.
func ensureVersion(log *logging.SimpleLogger, dl Downloader, versions map[string]string, v *version.Version, binDir string) (string, error) {
func ensureVersion(log *logging.SimpleLogger, dl Downloader, versions map[string]string, v *version.Version, binDir string, downloadURL string) (string, error) {
if binPath, ok := versions[v.String()]; ok {
return binPath, nil
}
Expand All @@ -411,9 +417,8 @@ func ensureVersion(log *logging.SimpleLogger, dl Downloader, versions map[string
versions[v.String()] = dest
return dest, nil
}

log.Info("could not find terraform version %s in PATH or %s, downloading from %s", v.String(), binDir, releasesURL)
urlPrefix := fmt.Sprintf("%s/terraform/%s/terraform_%s", releasesURL, v.String(), v.String())
log.Info("could not find terraform version %s in PATH or %s, downloading from %s", v.String(), binDir, downloadURL)
urlPrefix := fmt.Sprintf("%s/terraform/%s/terraform_%s", downloadURL, v.String(), v.String())
binURL := fmt.Sprintf("%s_%s_%s.zip", urlPrefix, runtime.GOOS, runtime.GOARCH)
checksumURL := fmt.Sprintf("%s_SHA256SUMS", urlPrefix)
if err := dl.GetFile(dest, fmt.Sprintf("%s?checksum=file:%s", binURL, checksumURL)); err != nil {
Expand Down
24 changes: 12 additions & 12 deletions server/events/terraform/terraform_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ is 0.11.13. You can update by downloading from www.terraform.io/downloads.html
Ok(t, err)
defer tempSetEnv(t, "PATH", fmt.Sprintf("%s:%s", tmp, os.Getenv("PATH")))()

c, err := terraform.NewClient(nil, tmp, "", "", "", cmd.DefaultTFVersionFlag, nil)
c, err := terraform.NewClient(nil, tmp, "", "", "", cmd.DefaultTFVersionFlag, cmd.TFDownloadURLFlag, nil)
Ok(t, err)

Ok(t, err)
Expand Down Expand Up @@ -96,7 +96,7 @@ is 0.11.13. You can update by downloading from www.terraform.io/downloads.html
Ok(t, err)
defer tempSetEnv(t, "PATH", fmt.Sprintf("%s:%s", tmp, os.Getenv("PATH")))()

c, err := terraform.NewClient(nil, tmp, "", "", "0.11.10", cmd.DefaultTFVersionFlag, nil)
c, err := terraform.NewClient(nil, tmp, "", "", "0.11.10", cmd.DefaultTFVersionFlag, cmd.TFDownloadURLFlag, nil)
Ok(t, err)

Ok(t, err)
Expand All @@ -116,7 +116,7 @@ func TestNewClient_NoTF(t *testing.T) {
// Set PATH to only include our empty directory.
defer tempSetEnv(t, "PATH", tmp)()

_, err := terraform.NewClient(nil, tmp, "", "", "", cmd.DefaultTFVersionFlag, nil)
_, err := terraform.NewClient(nil, tmp, "", "", "", cmd.DefaultTFVersionFlag, cmd.TFDownloadURLFlag, nil)
ErrEquals(t, "terraform not found in $PATH. Set --default-tf-version or download terraform from https://www.terraform.io/downloads.html", err)
}

Expand All @@ -133,7 +133,7 @@ func TestNewClient_DefaultTFFlagInPath(t *testing.T) {
Ok(t, err)
defer tempSetEnv(t, "PATH", fmt.Sprintf("%s:%s", tmp, os.Getenv("PATH")))()

c, err := terraform.NewClient(nil, tmp, "", "", "0.11.10", cmd.DefaultTFVersionFlag, nil)
c, err := terraform.NewClient(nil, tmp, "", "", "0.11.10", cmd.DefaultTFVersionFlag, cmd.TFDownloadURLFlag, nil)
Ok(t, err)

Ok(t, err)
Expand All @@ -157,7 +157,7 @@ func TestNewClient_DefaultTFFlagInBinDir(t *testing.T) {
Ok(t, err)
defer tempSetEnv(t, "PATH", fmt.Sprintf("%s:%s", tmp, os.Getenv("PATH")))()

c, err := terraform.NewClient(logging.NewNoopLogger(), tmp, "", "", "0.11.10", cmd.DefaultTFVersionFlag, nil)
c, err := terraform.NewClient(logging.NewNoopLogger(), tmp, "", "", "0.11.10", cmd.DefaultTFVersionFlag, cmd.TFDownloadURLFlag, nil)
Ok(t, err)

Ok(t, err)
Expand All @@ -183,12 +183,12 @@ func TestNewClient_DefaultTFFlagDownload(t *testing.T) {
err := ioutil.WriteFile(params[0].(string), []byte("#!/bin/sh\necho '\nTerraform v0.11.10\n'"), 0755)
return []pegomock.ReturnValue{err}
})
c, err := terraform.NewClient(nil, tmp, "", "", "0.11.10", cmd.DefaultTFVersionFlag, mockDownloader)
c, err := terraform.NewClient(nil, tmp, "", "", "0.11.10", cmd.DefaultTFVersionFlag, cmd.TFDownloadURLFlag, mockDownloader)
Ok(t, err)

Ok(t, err)
Equals(t, "0.11.10", c.DefaultVersion().String())
baseURL := "https://releases.hashicorp.com/terraform/0.11.10"
baseURL := fmt.Sprintf("%s/terraform/0.11.10", cmd.TFDownloadURLFlag)
expURL := fmt.Sprintf("%s/terraform_0.11.10_%s_%s.zip?checksum=file:%s/terraform_0.11.10_SHA256SUMS",
baseURL,
runtime.GOOS,
Expand All @@ -207,7 +207,7 @@ func TestNewClient_DefaultTFFlagDownload(t *testing.T) {
func TestNewClient_BadVersion(t *testing.T) {
tmp, cleanup := TempDir(t)
defer cleanup()
_, err := terraform.NewClient(nil, tmp, "", "", "malformed", cmd.DefaultTFVersionFlag, nil)
_, err := terraform.NewClient(nil, tmp, "", "", "malformed", cmd.DefaultTFVersionFlag, cmd.TFDownloadURLFlag, nil)
ErrEquals(t, "Malformed version: malformed", err)
}

Expand All @@ -219,7 +219,7 @@ func TestRunCommandWithVersion_DLsTF(t *testing.T) {

mockDownloader := mocks.NewMockDownloader()
// Set up our mock downloader to write a fake tf binary when it's called.
baseURL := "https://releases.hashicorp.com/terraform/99.99.99"
baseURL := fmt.Sprintf("%s/terraform/99.99.99", cmd.TFDownloadURLFlag)
expURL := fmt.Sprintf("%s/terraform_99.99.99_%s_%s.zip?checksum=file:%s/terraform_99.99.99_SHA256SUMS",
baseURL,
runtime.GOOS,
Expand All @@ -230,7 +230,7 @@ func TestRunCommandWithVersion_DLsTF(t *testing.T) {
return []pegomock.ReturnValue{err}
})

c, err := terraform.NewClient(nil, tmp, "", "", "0.11.10", cmd.DefaultTFVersionFlag, mockDownloader)
c, err := terraform.NewClient(nil, tmp, "", "", "0.11.10", cmd.DefaultTFVersionFlag, cmd.TFDownloadURLFlag, mockDownloader)
Ok(t, err)
Equals(t, "0.11.10", c.DefaultVersion().String())

Expand All @@ -249,7 +249,7 @@ func TestEnsureVersion_downloaded(t *testing.T) {

mockDownloader := mocks.NewMockDownloader()

c, err := terraform.NewClient(nil, tmp, "", "", "0.11.10", cmd.DefaultTFVersionFlag, mockDownloader)
c, err := terraform.NewClient(nil, tmp, "", "", "0.11.10", cmd.DefaultTFVersionFlag, cmd.TFDownloadURLFlag, mockDownloader)
Ok(t, err)

Equals(t, "0.11.10", c.DefaultVersion().String())
Expand All @@ -261,7 +261,7 @@ func TestEnsureVersion_downloaded(t *testing.T) {

Ok(t, err)

baseURL := "https://releases.hashicorp.com/terraform/99.99.99"
baseURL := fmt.Sprintf("%s/terraform/99.99.99", cmd.TFDownloadURLFlag)
expURL := fmt.Sprintf("%s/terraform_99.99.99_%s_%s.zip?checksum=file:%s/terraform_99.99.99_SHA256SUMS",
baseURL,
runtime.GOOS,
Expand Down
2 changes: 1 addition & 1 deletion server/events_controller_e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@ func setupE2E(t *testing.T, repoDir string) (server.EventsController, *vcsmocks.
GithubUser: "github-user",
GitlabUser: "gitlab-user",
}
terraformClient, err := terraform.NewClient(logger, dataDir, "", "", "", "default-tf-version", &NoopTFDownloader{})
terraformClient, err := terraform.NewClient(logger, dataDir, "", "", "", "tfdownloadurl", "default-tf-version", &NoopTFDownloader{})
Ok(t, err)
boltdb, err := db.New(dataDir)
Ok(t, err)
Expand Down
1 change: 1 addition & 0 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) {
userConfig.DataDir,
userConfig.TFEToken,
userConfig.TFEHostname,
userConfig.TFDownloadURL,
userConfig.DefaultTFVersion,
config.DefaultTFVersionFlag,
&terraform.DefaultDownloader{})
Expand Down
1 change: 1 addition & 0 deletions server/user_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type UserConfig struct {
SlackToken string `mapstructure:"slack-token"`
SSLCertFile string `mapstructure:"ssl-cert-file"`
SSLKeyFile string `mapstructure:"ssl-key-file"`
TFDownloadURL string `mapstructure:"tf-download-url"`
TFEHostname string `mapstructure:"tfe-hostname"`
TFEToken string `mapstructure:"tfe-token"`
DefaultTFVersion string `mapstructure:"default-tf-version"`
Expand Down

0 comments on commit c864731

Please sign in to comment.