From c1650fc02fac5d509573203119a1d986599cfa74 Mon Sep 17 00:00:00 2001 From: Julien WITTOUCK Date: Mon, 3 Feb 2020 18:43:24 +0100 Subject: [PATCH 1/5] :rotating_light: : fix compiler warnings --- src/main/java/io/codeka/gaia/hcl/HclParserImpl.kt | 2 +- src/main/java/io/codeka/gaia/modules/bo/TerraformImage.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/codeka/gaia/hcl/HclParserImpl.kt b/src/main/java/io/codeka/gaia/hcl/HclParserImpl.kt index db9bb89fe..e9af33264 100644 --- a/src/main/java/io/codeka/gaia/hcl/HclParserImpl.kt +++ b/src/main/java/io/codeka/gaia/hcl/HclParserImpl.kt @@ -12,7 +12,7 @@ interface HclParser { fun parseContent(content: String): HclVisitor fun parseVariables(content: String): List fun parseOutputs(content: String): List - fun parseProvider(fileContent: String): String + fun parseProvider(content: String): String } @Service diff --git a/src/main/java/io/codeka/gaia/modules/bo/TerraformImage.kt b/src/main/java/io/codeka/gaia/modules/bo/TerraformImage.kt index 4d35f8725..86d17f45f 100644 --- a/src/main/java/io/codeka/gaia/modules/bo/TerraformImage.kt +++ b/src/main/java/io/codeka/gaia/modules/bo/TerraformImage.kt @@ -3,7 +3,7 @@ package io.codeka.gaia.modules.bo import javax.validation.constraints.NotBlank import javax.validation.constraints.Pattern -data class TerraformImage @JvmOverloads constructor( +data class TerraformImage constructor( @field:NotBlank @field:Pattern(regexp = """^[\w][\w.\-\/]{0,127}$""") val repository: String, @field:NotBlank val tag: String) { From 93302fecbacb93d7c8d0f44331245c7aa321509e Mon Sep 17 00:00:00 2001 From: Julien WITTOUCK Date: Tue, 4 Feb 2020 19:50:30 +0100 Subject: [PATCH 2/5] :lock: : extract state api security configuration add a generated user wich can be used only with basic auth and STATE role --- .../gaia/config/security/SecurityConfig.java | 1 - .../security/StateApiSecurityConfig.java | 77 +++++++++++++++++++ .../security/StateApiSecurityConfigIT.java | 52 +++++++++++++ 3 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 src/main/java/io/codeka/gaia/config/security/StateApiSecurityConfig.java create mode 100644 src/test/java/io/codeka/gaia/config/security/StateApiSecurityConfigIT.java diff --git a/src/main/java/io/codeka/gaia/config/security/SecurityConfig.java b/src/main/java/io/codeka/gaia/config/security/SecurityConfig.java index 37655ca5b..7673a3170 100644 --- a/src/main/java/io/codeka/gaia/config/security/SecurityConfig.java +++ b/src/main/java/io/codeka/gaia/config/security/SecurityConfig.java @@ -35,7 +35,6 @@ protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeRequests() - .antMatchers("/api/state/**").permitAll() .antMatchers("/webjars/**").permitAll() .antMatchers("/css/**", "/js/**", "/favicon.ico", "/images/**").permitAll() .antMatchers("/**").authenticated() diff --git a/src/main/java/io/codeka/gaia/config/security/StateApiSecurityConfig.java b/src/main/java/io/codeka/gaia/config/security/StateApiSecurityConfig.java new file mode 100644 index 000000000..1f0e2238b --- /dev/null +++ b/src/main/java/io/codeka/gaia/config/security/StateApiSecurityConfig.java @@ -0,0 +1,77 @@ +package io.codeka.gaia.config.security; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.crypto.password.PasswordEncoder; + +import java.util.UUID; + +@Configuration +@Order(69) +public class StateApiSecurityConfig extends WebSecurityConfigurerAdapter { + + private PasswordEncoder bCrypt; + + private static final Log logger = LogFactory.getLog(StateApiSecurityConfig.class); + + @Autowired + public StateApiSecurityConfig(PasswordEncoder bCrypt) { + this.bCrypt = bCrypt; + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .antMatcher("/api/state/**") + .authorizeRequests() + .anyRequest().hasRole("STATE") + .and() + .httpBasic(); + } + + @Override + public void configure(AuthenticationManagerBuilder auth) throws Exception { + logger.info(String.format("%n%nUsing generated security password for state API: %s%n", properties().getPassword())); + + // configure default admin user + auth + .inMemoryAuthentication() + .withUser(properties().getUsername()).password(bCrypt.encode(properties().getPassword())).authorities("ROLE_STATE"); + } + + @Bean + @ConfigurationProperties(prefix = "gaia.state.api") + public StateApiSecurityProperties properties(){ + return new StateApiSecurityProperties("gaia-backend", UUID.randomUUID().toString()); + } + + public static class StateApiSecurityProperties { + + private String password; + + private String username; + + public StateApiSecurityProperties(String username, String password) { + this.username = username; + this.password = password; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + + } + +} \ No newline at end of file diff --git a/src/test/java/io/codeka/gaia/config/security/StateApiSecurityConfigIT.java b/src/test/java/io/codeka/gaia/config/security/StateApiSecurityConfigIT.java new file mode 100644 index 000000000..fdd85dafb --- /dev/null +++ b/src/test/java/io/codeka/gaia/config/security/StateApiSecurityConfigIT.java @@ -0,0 +1,52 @@ +package io.codeka.gaia.config.security; + +import io.codeka.gaia.test.MongoContainer; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.web.servlet.MockMvc; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; +import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated; +import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; + +@SpringBootTest +@AutoConfigureMockMvc +@DirtiesContext +@Testcontainers +public class StateApiSecurityConfigIT { + + @Container + private static MongoContainer mongoContainer = new MongoContainer(); + + @Autowired + private StateApiSecurityConfig.StateApiSecurityProperties props; + + @Autowired + private MockMvc mockMvc; + + @Test + void gaiaBackend_shouldHaveAccessToStateApi() throws Exception { + mockMvc.perform(get("/api/state/test").with(httpBasic(props.getUsername(), props.getPassword()))) + .andExpect(authenticated().withUsername("gaia-backend").withRoles("STATE")); + } + + @Test + void gaiaBackend_shouldNotHaveAccessToOtherApis() throws Exception { + mockMvc.perform(get("/api/modules/test").with(httpBasic(props.getUsername(), props.getPassword()))) + .andExpect(unauthenticated()); + } + + @Test + void gaiaBackend_shouldNotHaveAccessToScreens() throws Exception { + mockMvc.perform(get("/modules/test").with(httpBasic(props.getUsername(), props.getPassword()))) + .andExpect(unauthenticated()); + } + + +} \ No newline at end of file From 01cded579c1b2e488734440e4fa17bcf65feace3 Mon Sep 17 00:00:00 2001 From: Julien WITTOUCK Date: Tue, 4 Feb 2020 19:51:59 +0100 Subject: [PATCH 3/5] :sparkles: : use generated user to configure terraform backend --- .../gaia/runner/StackCommandBuilder.java | 7 ++++++- .../stacks/bo/mustache/TerraformScript.java | 20 +++++++++++++++++++ .../controller/UserControllerAdvice.java | 7 +++++++ .../resources/mustache/terraform.mustache | 2 ++ .../gaia/runner/StackCommandBuilderTest.java | 6 +++++- 5 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/codeka/gaia/runner/StackCommandBuilder.java b/src/main/java/io/codeka/gaia/runner/StackCommandBuilder.java index 085ec162c..6613ad629 100644 --- a/src/main/java/io/codeka/gaia/runner/StackCommandBuilder.java +++ b/src/main/java/io/codeka/gaia/runner/StackCommandBuilder.java @@ -1,6 +1,7 @@ package io.codeka.gaia.runner; import com.github.mustachejava.Mustache; +import io.codeka.gaia.config.security.StateApiSecurityConfig; import io.codeka.gaia.modules.bo.TerraformModule; import io.codeka.gaia.registries.RegistryOAuth2Provider; import io.codeka.gaia.settings.bo.Settings; @@ -23,14 +24,16 @@ public class StackCommandBuilder { private Settings settings; + private StateApiSecurityConfig.StateApiSecurityProperties stateApiSecurityProperties; private Mustache terraformMustache; private List registryOAuth2Providers; @Autowired - StackCommandBuilder(Settings settings, Mustache terraformMustache, List registryOAuth2Providers) { + StackCommandBuilder(Settings settings, Mustache terraformMustache, List registryOAuth2Providers, StateApiSecurityConfig.StateApiSecurityProperties stateApiSecurityProperties) { this.settings = settings; this.terraformMustache = terraformMustache; this.registryOAuth2Providers = registryOAuth2Providers; + this.stateApiSecurityProperties = stateApiSecurityProperties; } /** @@ -53,6 +56,8 @@ private String buildScript(Job job, Stack stack, TerraformModule module, BiFunction command) { var script = new TerraformScript() .setExternalUrl(settings.getExternalUrl()) + .setStateApiUser(stateApiSecurityProperties.getUsername()) + .setStateApiPassword(stateApiSecurityProperties.getPassword()) .setStackId(stack.getId()) .setGitRepositoryUrl(evalGitRepositoryUrl(module)) .setTerraformImage(job.getTerraformImage().image()); diff --git a/src/main/java/io/codeka/gaia/stacks/bo/mustache/TerraformScript.java b/src/main/java/io/codeka/gaia/stacks/bo/mustache/TerraformScript.java index 153d19ead..90c114cdc 100644 --- a/src/main/java/io/codeka/gaia/stacks/bo/mustache/TerraformScript.java +++ b/src/main/java/io/codeka/gaia/stacks/bo/mustache/TerraformScript.java @@ -11,6 +11,8 @@ public class TerraformScript { private String externalUrl; private String stackId; private String command; + private String stateApiUser; + private String stateApiPassword; public String getTerraformImage() { return terraformImage; @@ -65,4 +67,22 @@ public TerraformScript setCommand(String command) { this.command = command; return this; } + + public String getStateApiUser() { + return stateApiUser; + } + + public TerraformScript setStateApiUser(String stateApiUser) { + this.stateApiUser = stateApiUser; + return this; + } + + public String getStateApiPassword() { + return stateApiPassword; + } + + public TerraformScript setStateApiPassword(String stateApiPassword) { + this.stateApiPassword = stateApiPassword; + return this; + } } diff --git a/src/main/java/io/codeka/gaia/teams/controller/UserControllerAdvice.java b/src/main/java/io/codeka/gaia/teams/controller/UserControllerAdvice.java index 0ae3dff8a..9d7e5a94c 100644 --- a/src/main/java/io/codeka/gaia/teams/controller/UserControllerAdvice.java +++ b/src/main/java/io/codeka/gaia/teams/controller/UserControllerAdvice.java @@ -24,6 +24,9 @@ public User user(Authentication authentication) { if (authentication == null) { return null; } + if ("gaia-backend".equals(authentication.getName())) { + return null; + } return userRepository.findById(authentication.getName()).orElseThrow(); } @@ -33,6 +36,10 @@ public Team userTeam(Authentication authentication, @ModelAttribute User user) { if (authentication == null) { return null; } + // in case of state access only + if ("gaia-backend".equals(authentication.getName())) { + return null; + } return user.getTeam(); } diff --git a/src/main/resources/mustache/terraform.mustache b/src/main/resources/mustache/terraform.mustache index a3a874886..10dde21de 100644 --- a/src/main/resources/mustache/terraform.mustache +++ b/src/main/resources/mustache/terraform.mustache @@ -14,6 +14,8 @@ echo 'generating backend configuration' echo 'terraform { backend "http" { address = "{{externalUrl}}/api/state/{{stackId}}" + username = "{{stateApiUser}}" + password = "{{stateApiPassword}}" } }' > backend.tf diff --git a/src/test/java/io/codeka/gaia/runner/StackCommandBuilderTest.java b/src/test/java/io/codeka/gaia/runner/StackCommandBuilderTest.java index adcb69781..b6cf73fc5 100644 --- a/src/test/java/io/codeka/gaia/runner/StackCommandBuilderTest.java +++ b/src/test/java/io/codeka/gaia/runner/StackCommandBuilderTest.java @@ -1,6 +1,7 @@ package io.codeka.gaia.runner; import com.github.mustachejava.DefaultMustacheFactory; +import io.codeka.gaia.config.security.StateApiSecurityConfig; import io.codeka.gaia.modules.bo.TerraformModule; import io.codeka.gaia.modules.bo.Variable; import io.codeka.gaia.registries.RegistryOAuth2Provider; @@ -35,7 +36,10 @@ class StackCommandBuilderTest { @BeforeEach void setup() { var mustache = new DefaultMustacheFactory().compile("mustache/terraform.mustache"); - stackCommandBuilder = new StackCommandBuilder(new Settings(), mustache, List.of(registryOAuth2Provider)); + + var stateApiSecurityProperties = new StateApiSecurityConfig.StateApiSecurityProperties("gaia-backend", "password"); + + stackCommandBuilder = new StackCommandBuilder(new Settings(), mustache, List.of(registryOAuth2Provider), stateApiSecurityProperties); } @Test From a553d1a8c11ff9db9526236cb488d77275b7ce6b Mon Sep 17 00:00:00 2001 From: Julien WITTOUCK Date: Tue, 4 Feb 2020 19:53:32 +0100 Subject: [PATCH 4/5] :bug: : users should be able to get their states --- .../java/io/codeka/gaia/config/security/SecurityConfig.java | 2 +- .../io/codeka/gaia/config/security/StateApiSecurityConfig.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/codeka/gaia/config/security/SecurityConfig.java b/src/main/java/io/codeka/gaia/config/security/SecurityConfig.java index 7673a3170..6626f22bd 100644 --- a/src/main/java/io/codeka/gaia/config/security/SecurityConfig.java +++ b/src/main/java/io/codeka/gaia/config/security/SecurityConfig.java @@ -49,7 +49,7 @@ public void configure(AuthenticationManagerBuilder auth) throws Exception { // configure default admin user auth .inMemoryAuthentication() - .withUser("admin").password(bcrypt().encode(adminPassword)).authorities("ROLE_ADMIN") + .withUser("admin").password(bcrypt().encode(adminPassword)).authorities("ROLE_ADMIN", "ROLE_USER") .and() .withUser("user").password(bcrypt().encode("user123")).authorities("ROLE_USER"); } diff --git a/src/main/java/io/codeka/gaia/config/security/StateApiSecurityConfig.java b/src/main/java/io/codeka/gaia/config/security/StateApiSecurityConfig.java index 1f0e2238b..a8e657a0a 100644 --- a/src/main/java/io/codeka/gaia/config/security/StateApiSecurityConfig.java +++ b/src/main/java/io/codeka/gaia/config/security/StateApiSecurityConfig.java @@ -32,7 +32,7 @@ protected void configure(HttpSecurity http) throws Exception { http .antMatcher("/api/state/**") .authorizeRequests() - .anyRequest().hasRole("STATE") + .anyRequest().hasAnyRole("STATE", "USER") .and() .httpBasic(); } From 3c8981afbd5d7630b2efdb9cfb8e198e3ef90f06 Mon Sep 17 00:00:00 2001 From: Julien WITTOUCK Date: Wed, 5 Feb 2020 13:19:33 +0100 Subject: [PATCH 5/5] :white_check_mark: : cover job and stack page with percy --- src/main/resources/templates/stack.html | 2 +- src/test/java/io/codeka/gaia/e2e/JobPage.kt | 15 ++ .../java/io/codeka/gaia/e2e/SeleniumIT.java | 32 ++++- src/test/java/io/codeka/gaia/e2e/StackPage.kt | 20 +++ .../controller/StackRestControllerIT.java | 4 +- src/test/resources/db/30_stack.js | 23 +++ src/test/resources/db/40_job.js | 59 ++++++++ src/test/resources/db/50_step.js | 48 +++++++ src/test/resources/db/60_terraformState.js | 132 ++++++++++++++++++ 9 files changed, 329 insertions(+), 6 deletions(-) create mode 100644 src/test/java/io/codeka/gaia/e2e/JobPage.kt create mode 100644 src/test/java/io/codeka/gaia/e2e/StackPage.kt create mode 100644 src/test/resources/db/40_job.js create mode 100644 src/test/resources/db/50_step.js create mode 100644 src/test/resources/db/60_terraformState.js diff --git a/src/main/resources/templates/stack.html b/src/main/resources/templates/stack.html index 4000efa1c..89ec51608 100644 --- a/src/main/resources/templates/stack.html +++ b/src/main/resources/templates/stack.html @@ -103,7 +103,7 @@

- 2.6\"\n\n\u001b[0m\u001b[1m\u001b[32mTerraform has been successfully initialized!\u001b[0m\u001b[32m\u001b[0m\n\u001b[0m\u001b[1mdocker_image.mongo: Creating...\u001b[0m\u001b[0m\n\u001b[0m\u001b[1mdocker_image.mongo: Creation complete after 0s [id=sha256:5976dac61f4fb85c1a2d1f7c717600f9c78fb02badba6b3c5961a4091ef75905mongo]\u001b[0m\u001b[0m\n\u001b[0m\u001b[1mdocker_container.mongo: Creating...\u001b[0m\u001b[0m\n\u001b[0m\u001b[1mdocker_container.mongo: Creation complete after 1s [id=b247c86c6cee2c3ad6e9565c48cae29877374d07f7c6be5f42489002bdf66b42]\u001b[0m\u001b[0m\n\u001b[0m\u001b[1m\u001b[32m\nApply complete! Resources: 2 added, 0 changed, 0 destroyed.\u001b[0m\n\u001b[0m\u001b[1m\u001b[32m\nOutputs:\n\ndocker_container_name = test\u001b[0m\n", + "_class" : "io.codeka.gaia.stacks.bo.Step" + }, + { + "_id" : "0ddcb316-8681-4416-bc59-f3e7d47836f1", + "jobId" : "5e856dc7-6bed-465f-abf1-02980206ab2a", + "startDateTime" : ISODate("2020-02-05T07:01:30.875Z"), + "endDateTime" : ISODate("2020-02-05T07:01:37.393Z"), + "executionTime" : NumberLong(6518), + "type" : "PLAN", + "status" : "FINISHED", + "logs" : "", + "_class" : "io.codeka.gaia.stacks.bo.Step" + }, + { + "_id" : "e294e7a2-abb7-4c0b-9102-5fb72ad42292", + "jobId" : "5e856dc7-6bed-465f-abf1-02980206ab2a", + "startDateTime" : ISODate("2020-02-05T07:02:13.086Z"), + "endDateTime" : ISODate("2020-02-05T07:02:18.625Z"), + "executionTime" : NumberLong(5539), + "type" : "APPLY", + "status" : "FINISHED", + "logs" : "using image hashicorp/terraform:latest\ncloning https://oauth2:[MASKED]@github.com/juwit/terraform-docker-mongo\nCloning into 'module'...\ngenerating backend configuration\nTerraform v0.12.20\n\n\u001b[0m\u001b[1mInitializing the backend...\u001b[0m\n\u001b[0m\u001b[32m\nSuccessfully configured the backend \"http\"! Terraform will automatically\nuse this backend unless the backend configuration changes.\u001b[0m\n\n\u001b[0m\u001b[1mInitializing provider plugins...\u001b[0m\n- Checking for available provider plugins...\n- Downloading plugin for provider \"docker\" (terraform-providers/docker) 2.6.0...\n\nThe following providers do not have any version constraints in configuration,\nso the latest version was installed.\n\nTo prevent automatic upgrades to new major versions that may contain breaking\nchanges, it is recommended to add version = \"...\" constraints to the\ncorresponding provider blocks in configuration, with the constraint strings\nsuggested below.\n\n* provider.docker: version = \"~> 2.6\"\n\n\u001b[0m\u001b[1m\u001b[32mTerraform has been successfully initialized!\u001b[0m\u001b[32m\u001b[0m\n\u001b[0m\u001b[1mdocker_image.mongo: Refreshing state... [id=sha256:5976dac61f4fb85c1a2d1f7c717600f9c78fb02badba6b3c5961a4091ef75905mongo]\u001b[0m\n\u001b[0m\u001b[1mdocker_container.mongo: Refreshing state... [id=b247c86c6cee2c3ad6e9565c48cae29877374d07f7c6be5f42489002bdf66b42]\u001b[0m\n\u001b[0m\u001b[1mdocker_container.mongo: Creating...\u001b[0m\u001b[0m\n\u001b[0m\u001b[1mdocker_container.mongo: Creation complete after 0s [id=330b26c77d65e106df926454f9c3c3448f30c4a79062daefe66fe1d4a8f2970e]\u001b[0m\u001b[0m\n\u001b[0m\u001b[1m\u001b[32m\nApply complete! Resources: 1 added, 0 changed, 0 destroyed.\u001b[0m\n\u001b[0m\u001b[1m\u001b[32m\nOutputs:\n\ndocker_container_name = local-mongo\u001b[0m\n", + "_class" : "io.codeka.gaia.stacks.bo.Step" + } +]); \ No newline at end of file diff --git a/src/test/resources/db/60_terraformState.js b/src/test/resources/db/60_terraformState.js new file mode 100644 index 000000000..77c293026 --- /dev/null +++ b/src/test/resources/db/60_terraformState.js @@ -0,0 +1,132 @@ +gaia = db.getSiblingDB('gaia'); +gaia.terraformState.drop(); +gaia.terraformState.insert([ + { + "_id" : "de28a01f-257a-448d-8e1b-00e4e3a41db2", + "value" : { + "version" : 4, + "terraform_version" : "0.12.20", + "serial" : 0, + "lineage" : "ea06e653-352b-ec53-90c5-58699c6c9043", + "outputs" : { + "docker_container_name" : { + "value" : "test", + "type" : "string" + } + }, + "resources" : [ + { + "mode" : "managed", + "type" : "docker_container", + "name" : "mongo", + "provider" : "provider.docker", + "instances" : [ + { + "schema_version" : 1, + "attributes" : { + "attach" : false, + "bridge" : "", + "capabilities" : [], + "command" : null, + "container_logs" : null, + "cpu_set" : null, + "cpu_shares" : null, + "destroy_grace_seconds" : null, + "devices" : [], + "dns" : null, + "dns_opts" : null, + "dns_search" : null, + "domainname" : null, + "entrypoint" : null, + "env" : null, + "exit_code" : null, + "gateway" : "172.17.0.1", + "group_add" : null, + "healthcheck" : [], + "host" : [], + "hostname" : null, + "id" : "b247c86c6cee2c3ad6e9565c48cae29877374d07f7c6be5f42489002bdf66b42", + "image" : "sha256:5976dac61f4fb85c1a2d1f7c717600f9c78fb02badba6b3c5961a4091ef75905", + "ip_address" : "172.17.0.3", + "ip_prefix_length" : 16, + "ipc_mode" : null, + "labels" : null, + "links" : null, + "log_driver" : "json-file", + "log_opts" : null, + "logs" : false, + "max_retry_count" : null, + "memory" : null, + "memory_swap" : null, + "mounts" : [], + "must_run" : true, + "name" : "test", + "network_alias" : null, + "network_data" : [ + { + "gateway" : "172.17.0.1", + "ip_address" : "172.17.0.3", + "ip_prefix_length" : 16, + "network_name" : "bridge" + } + ], + "network_mode" : null, + "networks" : null, + "networks_advanced" : [], + "pid_mode" : null, + "ports" : [ + { + "external" : 28017, + "internal" : 27017, + "ip" : "0.0.0.0", + "protocol" : "tcp" + } + ], + "privileged" : null, + "publish_all_ports" : null, + "read_only" : false, + "restart" : "no", + "rm" : false, + "shm_size" : null, + "start" : true, + "sysctls" : null, + "tmpfs" : null, + "ulimit" : [], + "upload" : [], + "user" : null, + "userns_mode" : null, + "volumes" : [], + "working_dir" : null + }, + "private" : "eyJzY2hlbWFfdmVyc2lvbiI6IjEifQ==", + "dependencies" : [ + "docker_image.mongo" + ] + } + ] + }, + { + "mode" : "managed", + "type" : "docker_image", + "name" : "mongo", + "provider" : "provider.docker", + "instances" : [ + { + "schema_version" : 0, + "attributes" : { + "id" : "sha256:5976dac61f4fb85c1a2d1f7c717600f9c78fb02badba6b3c5961a4091ef75905mongo", + "keep_locally" : true, + "latest" : "sha256:5976dac61f4fb85c1a2d1f7c717600f9c78fb02badba6b3c5961a4091ef75905", + "name" : "mongo", + "pull_trigger" : null, + "pull_triggers" : null + }, + "private" : "bnVsbA==" + } + ] + } + ] + }, + "_class" : "io.codeka.gaia.stacks.bo.TerraformState" + } +]); \ No newline at end of file