Add support for App Platform (#497)

* Bump godo version.

* Initial support for app service spec.

* Update godo

* Add support for static sites.

* Refactor to share appSpecComponentBase

* Add support for workers.

* Fix expandAppDomainSpec

* Add database support.

* Add first set of acceptance tests.

* Add test excercising envs.

* Add worker test.

* Add import test.

* Add sweeper.

* Add App data source.

* Add documentation for the resource.

* Add data source docs.

* Update health_check attributes.

* Use basic plan in acceptance tests.

* Test upgrading an app from basic to professional.

* Update waitForAppDeployment method.

* Fix env docs.

* Update digitalocean/datasource_digitalocean_app_test.go

Co-authored-by: Cesar Garza <scotch.neat@live.com>

* Simplify expand methods.

* Fix typo in sweeper log message.

Co-authored-by: Cesar Garza <scotch.neat@live.com>
This commit is contained in:
Andrew Starr-Bochicchio 2020-10-13 10:16:53 -04:00 committed by GitHub
parent 4c6b74bb0d
commit 99f86f4aaa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 2418 additions and 146 deletions

846
digitalocean/app_spec.go Normal file
View File

@ -0,0 +1,846 @@
package digitalocean
import (
"log"
"github.com/digitalocean/godo"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
)
func appSpecSchema() map[string]*schema.Schema {
return map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringLenBetween(2, 32),
Description: "The name of the app. Must be unique across all apps in the same account.",
},
"region": {
Type: schema.TypeString,
Optional: true,
Description: "The slug for the DigitalOcean data center region hosting the app",
},
"domains": {
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"service": {
Type: schema.TypeList,
Optional: true,
MinItems: 1,
Elem: appSpecServicesSchema(),
},
"static_site": {
Type: schema.TypeList,
Optional: true,
Elem: appSpecStaticSiteSchema(),
},
"worker": {
Type: schema.TypeList,
Optional: true,
Elem: appSpecWorkerSchema(),
},
"database": {
Type: schema.TypeList,
Optional: true,
Elem: appSpecDatabaseSchema(),
},
}
}
func appSpecGitSourceSchema() map[string]*schema.Schema {
return map[string]*schema.Schema{
"repo_clone_url": {
Type: schema.TypeString,
Optional: true,
Description: "The clone URL of the repo.",
},
"branch": {
Type: schema.TypeString,
Optional: true,
Description: "The name of the branch to use.",
},
}
}
func appSpecGitHubSourceSchema() map[string]*schema.Schema {
return map[string]*schema.Schema{
"repo": {
Type: schema.TypeString,
Optional: true,
Description: "The name of the repo in the format `owner/repo`.",
},
"branch": {
Type: schema.TypeString,
Optional: true,
Description: "The name of the branch to use.",
},
"deploy_on_push": {
Type: schema.TypeBool,
Optional: true,
Description: "Whether to automatically deploy new commits made to the repo",
},
}
}
func appSpecEnvSchema() *schema.Resource {
return &schema.Resource{
Schema: map[string]*schema.Schema{
"key": {
Type: schema.TypeString,
Optional: true,
Description: "The name of the environment variable.",
},
"value": {
Type: schema.TypeString,
Optional: true,
Description: "The value of the environment variable.",
},
"scope": {
Type: schema.TypeString,
Optional: true,
Default: "RUN_AND_BUILD_TIME",
ValidateFunc: validation.StringInSlice([]string{
"UNSET",
"RUN_TIME",
"BUILD_TIME",
"RUN_AND_BUILD_TIME",
}, false),
Description: "The visibility scope of the environment variable.",
},
"type": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateFunc: validation.StringInSlice([]string{
"GENERAL",
"SECRET",
}, false),
Description: "The type of the environment variable.",
},
},
}
}
func appSpecRouteSchema() map[string]*schema.Schema {
return map[string]*schema.Schema{
"path": {
Type: schema.TypeString,
Optional: true,
Description: "Path specifies an route by HTTP path prefix. Paths must start with / and must be unique within the app.",
},
}
}
func appSpecHealthCheckSchema() map[string]*schema.Schema {
return map[string]*schema.Schema{
"http_path": {
Type: schema.TypeString,
Optional: true,
Description: "The route path used for the HTTP health check ping.",
},
"initial_delay_seconds": {
Type: schema.TypeInt,
Optional: true,
Description: "The number of seconds to wait before beginning health checks.",
},
"period_seconds": {
Type: schema.TypeInt,
Optional: true,
Description: "The number of seconds to wait between health checks.",
},
"timeout_seconds": {
Type: schema.TypeInt,
Optional: true,
Description: "The number of seconds after which the check times out.",
},
"success_threshold": {
Type: schema.TypeInt,
Optional: true,
Description: "The number of successful health checks before considered healthy.",
},
"failure_threshold": {
Type: schema.TypeInt,
Optional: true,
Description: "The number of failed health checks before considered unhealthy.",
},
}
}
func appSpecComponentBase() map[string]*schema.Schema {
return map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
Description: "The name of the component",
},
"git": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: appSpecGitSourceSchema(),
},
},
"github": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: appSpecGitHubSourceSchema(),
},
},
"dockerfile_path": {
Type: schema.TypeString,
Optional: true,
Description: "The path to a Dockerfile relative to the root of the repo. If set, overrides usage of buildpacks.",
},
"build_command": {
Type: schema.TypeString,
Optional: true,
Description: "An optional build command to run while building this component from source.",
},
"env": {
Type: schema.TypeSet,
Optional: true,
Elem: appSpecEnvSchema(),
Set: schema.HashResource(appSpecEnvSchema()),
},
"routes": {
Type: schema.TypeList,
Optional: true,
Computed: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: appSpecRouteSchema(),
},
},
"source_dir": {
Type: schema.TypeString,
Optional: true,
Description: "An optional path to the working directory to use for the build.",
},
"environment_slug": {
Type: schema.TypeString,
Optional: true,
Description: "An environment slug describing the type of this app.",
},
}
}
func appSpecServicesSchema() *schema.Resource {
serviceSchema := map[string]*schema.Schema{
"run_command": {
Type: schema.TypeString,
Optional: true,
Computed: true,
Description: "An optional run command to override the component's default.",
},
"http_port": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
Description: "The internal port on which this service's run command will listen.",
},
"instance_size_slug": {
Type: schema.TypeString,
Optional: true,
Description: "The instance size to use for this component.",
},
"instance_count": {
Type: schema.TypeInt,
Optional: true,
Default: 1,
Description: "The amount of instances that this component should be scaled to.",
},
"health_check": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: appSpecHealthCheckSchema(),
},
},
}
for k, v := range appSpecComponentBase() {
serviceSchema[k] = v
}
return &schema.Resource{
Schema: serviceSchema,
}
}
func appSpecStaticSiteSchema() *schema.Resource {
staticSiteSchema := map[string]*schema.Schema{
"output_dir": {
Type: schema.TypeString,
Optional: true,
Description: "An optional path to where the built assets will be located, relative to the build context. If not set, App Platform will automatically scan for these directory names: `_static`, `dist`, `public`.",
},
"index_document": {
Type: schema.TypeString,
Optional: true,
Description: "The name of the index document to use when serving this static site.",
},
"error_document": {
Type: schema.TypeString,
Optional: true,
Description: "The name of the error document to use when serving this static site.",
},
}
for k, v := range appSpecComponentBase() {
staticSiteSchema[k] = v
}
return &schema.Resource{
Schema: staticSiteSchema,
}
}
func appSpecWorkerSchema() *schema.Resource {
workerSchema := map[string]*schema.Schema{
"run_command": {
Type: schema.TypeString,
Optional: true,
Description: "An optional run command to override the component's default.",
},
"instance_size_slug": {
Type: schema.TypeString,
Optional: true,
Description: "The instance size to use for this component.",
},
"instance_count": {
Type: schema.TypeInt,
Optional: true,
Default: 1,
Description: "The amount of instances that this component should be scaled to.",
},
}
for k, v := range appSpecComponentBase() {
workerSchema[k] = v
}
return &schema.Resource{
Schema: workerSchema,
}
}
func appSpecDatabaseSchema() *schema.Resource {
return &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Optional: true,
Description: "The name of the component",
},
"engine": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice([]string{
"UNSET",
"MYSQL",
"PG",
"REDIS",
}, false),
Description: "The database engine to use.",
},
"version": {
Type: schema.TypeString,
Optional: true,
Description: "The version of the database engine.",
},
"production": {
Type: schema.TypeBool,
Optional: true,
Description: "Whether this is a production or dev database.",
},
"cluster_name": {
Type: schema.TypeString,
Optional: true,
Description: "The name of the underlying DigitalOcean DBaaS cluster. This is required for production databases. For dev databases, if cluster_name is not set, a new cluster will be provisioned.",
},
"db_name": {
Type: schema.TypeString,
Optional: true,
Description: "The name of the MySQL or PostgreSQL database to configure.",
},
"db_user": {
Type: schema.TypeString,
Optional: true,
Description: "The name of the MySQL or PostgreSQL user to configure.",
},
},
}
}
func expandAppSpec(config []interface{}) *godo.AppSpec {
if len(config) == 0 || config[0] == nil {
return &godo.AppSpec{}
}
appSpecConfig := config[0].(map[string]interface{})
appSpec := &godo.AppSpec{
Name: appSpecConfig["name"].(string),
Region: appSpecConfig["region"].(string),
Domains: expandAppDomainSpec(appSpecConfig["domains"].(*schema.Set).List()),
Services: expandAppSpecServices(appSpecConfig["service"].([]interface{})),
StaticSites: expandAppSpecStaticSites(appSpecConfig["static_site"].([]interface{})),
Workers: expandAppSpecWorkers(appSpecConfig["worker"].([]interface{})),
Databases: expandAppSpecDatabases(appSpecConfig["database"].([]interface{})),
}
return appSpec
}
func flattenAppSpec(spec *godo.AppSpec) []map[string]interface{} {
result := make([]map[string]interface{}, 0, 1)
if spec != nil {
r := make(map[string]interface{})
r["name"] = (*spec).Name
r["region"] = (*spec).Region
r["domains"] = flattenAppDomainSpec((*spec).Domains)
if len((*spec).Services) > 0 {
r["service"] = flattenAppSpecServices((*spec).Services)
}
if len((*spec).StaticSites) > 0 {
r["static_site"] = flattenAppSpecStaticSites((*spec).StaticSites)
}
if len((*spec).Workers) > 0 {
r["worker"] = flattenAppSpecWorkers((*spec).Workers)
}
if len((*spec).Databases) > 0 {
r["database"] = flattenAppSpecDatabases((*spec).Databases)
}
result = append(result, r)
}
return result
}
func expandAppDomainSpec(config []interface{}) []*godo.AppDomainSpec {
appDomains := make([]*godo.AppDomainSpec, 0, len(config))
for _, rawDomain := range config {
domain := &godo.AppDomainSpec{
Domain: rawDomain.(string),
}
appDomains = append(appDomains, domain)
}
return appDomains
}
func flattenAppDomainSpec(spec []*godo.AppDomainSpec) *schema.Set {
result := schema.NewSet(schema.HashString, []interface{}{})
for _, domain := range spec {
result.Add(domain.Domain)
}
return result
}
func expandAppGitHubSourceSpec(config []interface{}) *godo.GitHubSourceSpec {
gitHubSourceConfig := config[0].(map[string]interface{})
gitHubSource := &godo.GitHubSourceSpec{
Repo: gitHubSourceConfig["repo"].(string),
Branch: gitHubSourceConfig["branch"].(string),
DeployOnPush: gitHubSourceConfig["deploy_on_push"].(bool),
}
return gitHubSource
}
func flattenAppGitHubSourceSpec(spec *godo.GitHubSourceSpec) []interface{} {
result := make([]interface{}, 0)
if spec != nil {
r := make(map[string]interface{})
r["repo"] = (*spec).Repo
r["branch"] = (*spec).Branch
r["deploy_on_push"] = (*spec).DeployOnPush
result = append(result, r)
}
return result
}
func expandAppGitSourceSpec(config []interface{}) *godo.GitSourceSpec {
gitSourceConfig := config[0].(map[string]interface{})
gitSource := &godo.GitSourceSpec{
Branch: gitSourceConfig["branch"].(string),
RepoCloneURL: gitSourceConfig["repo_clone_url"].(string),
}
return gitSource
}
func flattenAppGitSourceSpec(spec *godo.GitSourceSpec) []interface{} {
result := make([]interface{}, 0)
if spec != nil {
r := make(map[string]interface{})
r["branch"] = (*spec).Branch
r["repo_clone_url"] = (*spec).RepoCloneURL
result = append(result, r)
}
return result
}
func expandAppEnvs(config []interface{}) []*godo.AppVariableDefinition {
appEnvs := make([]*godo.AppVariableDefinition, 0, len(config))
for _, rawEnv := range config {
env := rawEnv.(map[string]interface{})
e := &godo.AppVariableDefinition{
Value: env["value"].(string),
Scope: godo.AppVariableScope(env["scope"].(string)),
Key: env["key"].(string),
Type: godo.AppVariableType(env["type"].(string)),
}
appEnvs = append(appEnvs, e)
}
return appEnvs
}
func flattenAppEnvs(appEnvs []*godo.AppVariableDefinition) *schema.Set {
result := schema.NewSet(schema.HashResource(appSpecEnvSchema()), []interface{}{})
for _, env := range appEnvs {
r := make(map[string]interface{})
r["value"] = env.Value
r["scope"] = string(env.Scope)
r["key"] = env.Key
r["type"] = string(env.Type)
result.Add(r)
setFunc := schema.HashResource(appSpecEnvSchema())
log.Printf("[DEBUG] App env hash for %s: %d", r["key"], setFunc(r))
}
return result
}
func expandAppHealthCheck(config []interface{}) *godo.AppServiceSpecHealthCheck {
healthCheckConfig := config[0].(map[string]interface{})
healthCheck := &godo.AppServiceSpecHealthCheck{
HTTPPath: healthCheckConfig["http_path"].(string),
InitialDelaySeconds: int32(healthCheckConfig["initial_delay_seconds"].(int)),
PeriodSeconds: int32(healthCheckConfig["period_seconds"].(int)),
TimeoutSeconds: int32(healthCheckConfig["timeout_seconds"].(int)),
SuccessThreshold: int32(healthCheckConfig["success_threshold"].(int)),
FailureThreshold: int32(healthCheckConfig["failure_threshold"].(int)),
}
return healthCheck
}
func flattenAppHealthCheck(check *godo.AppServiceSpecHealthCheck) []interface{} {
result := make([]interface{}, 0)
if check != nil {
r := make(map[string]interface{})
r["http_path"] = check.HTTPPath
r["initial_delay_seconds"] = check.InitialDelaySeconds
r["period_seconds"] = check.PeriodSeconds
r["timeout_seconds"] = check.TimeoutSeconds
r["success_threshold"] = check.SuccessThreshold
r["failure_threshold"] = check.FailureThreshold
result = append(result, r)
}
return result
}
func expandAppRoutes(config []interface{}) []*godo.AppRouteSpec {
appRoutes := make([]*godo.AppRouteSpec, 0, len(config))
for _, rawRoute := range config {
route := rawRoute.(map[string]interface{})
r := &godo.AppRouteSpec{
Path: route["path"].(string),
}
appRoutes = append(appRoutes, r)
}
return appRoutes
}
func flattenAppRoutes(routes []*godo.AppRouteSpec) []interface{} {
result := make([]interface{}, 0)
for _, route := range routes {
r := make(map[string]interface{})
r["path"] = route.Path
result = append(result, r)
}
return result
}
func expandAppSpecServices(config []interface{}) []*godo.AppServiceSpec {
appServices := make([]*godo.AppServiceSpec, 0, len(config))
for _, rawService := range config {
service := rawService.(map[string]interface{})
s := &godo.AppServiceSpec{
Name: service["name"].(string),
RunCommand: service["run_command"].(string),
BuildCommand: service["build_command"].(string),
HTTPPort: int64(service["http_port"].(int)),
DockerfilePath: service["dockerfile_path"].(string),
Envs: expandAppEnvs(service["env"].(*schema.Set).List()),
InstanceSizeSlug: service["instance_size_slug"].(string),
InstanceCount: int64(service["instance_count"].(int)),
SourceDir: service["source_dir"].(string),
EnvironmentSlug: service["environment_slug"].(string),
}
github := service["github"].([]interface{})
if len(github) > 0 {
s.GitHub = expandAppGitHubSourceSpec(github)
}
git := service["git"].([]interface{})
if len(git) > 0 {
s.Git = expandAppGitSourceSpec(git)
}
routes := service["routes"].([]interface{})
if len(routes) > 0 {
s.Routes = expandAppRoutes(routes)
}
checks := service["health_check"].([]interface{})
if len(checks) > 0 {
s.HealthCheck = expandAppHealthCheck(checks)
}
appServices = append(appServices, s)
}
return appServices
}
func flattenAppSpecServices(services []*godo.AppServiceSpec) []map[string]interface{} {
result := make([]map[string]interface{}, len(services))
for i, s := range services {
r := make(map[string]interface{})
r["name"] = s.Name
r["run_command"] = s.RunCommand
r["build_command"] = s.BuildCommand
r["github"] = flattenAppGitHubSourceSpec(s.GitHub)
r["git"] = flattenAppGitSourceSpec(s.Git)
r["http_port"] = int(s.HTTPPort)
r["routes"] = flattenAppRoutes(s.Routes)
r["dockerfile_path"] = s.DockerfilePath
r["env"] = flattenAppEnvs(s.Envs)
r["health_check"] = flattenAppHealthCheck(s.HealthCheck)
r["instance_size_slug"] = s.InstanceSizeSlug
r["instance_count"] = int(s.InstanceCount)
r["source_dir"] = s.SourceDir
r["environment_slug"] = s.EnvironmentSlug
result[i] = r
}
return result
}
func expandAppSpecStaticSites(config []interface{}) []*godo.AppStaticSiteSpec {
appSites := make([]*godo.AppStaticSiteSpec, 0, len(config))
for _, rawSite := range config {
site := rawSite.(map[string]interface{})
s := &godo.AppStaticSiteSpec{
Name: site["name"].(string),
BuildCommand: site["build_command"].(string),
DockerfilePath: site["dockerfile_path"].(string),
Envs: expandAppEnvs(site["env"].(*schema.Set).List()),
SourceDir: site["source_dir"].(string),
OutputDir: site["output_dir"].(string),
IndexDocument: site["index_document"].(string),
ErrorDocument: site["error_document"].(string),
EnvironmentSlug: site["environment_slug"].(string),
}
github := site["github"].([]interface{})
if len(github) > 0 {
s.GitHub = expandAppGitHubSourceSpec(github)
}
git := site["git"].([]interface{})
if len(git) > 0 {
s.Git = expandAppGitSourceSpec(git)
}
routes := site["routes"].([]interface{})
if len(routes) > 0 {
s.Routes = expandAppRoutes(routes)
}
appSites = append(appSites, s)
}
return appSites
}
func flattenAppSpecStaticSites(sites []*godo.AppStaticSiteSpec) []map[string]interface{} {
result := make([]map[string]interface{}, len(sites))
for i, s := range sites {
r := make(map[string]interface{})
r["name"] = s.Name
r["build_command"] = s.BuildCommand
r["github"] = flattenAppGitHubSourceSpec(s.GitHub)
r["git"] = flattenAppGitSourceSpec(s.Git)
r["routes"] = flattenAppRoutes(s.Routes)
r["dockerfile_path"] = s.DockerfilePath
r["env"] = flattenAppEnvs(s.Envs)
r["source_dir"] = s.SourceDir
r["output_dir"] = s.OutputDir
r["index_document"] = s.IndexDocument
r["error_document"] = s.ErrorDocument
r["environment_slug"] = s.EnvironmentSlug
result[i] = r
}
return result
}
func expandAppSpecWorkers(config []interface{}) []*godo.AppWorkerSpec {
appWorkers := make([]*godo.AppWorkerSpec, 0, len(config))
for _, rawWorker := range config {
worker := rawWorker.(map[string]interface{})
s := &godo.AppWorkerSpec{
Name: worker["name"].(string),
RunCommand: worker["run_command"].(string),
BuildCommand: worker["build_command"].(string),
DockerfilePath: worker["dockerfile_path"].(string),
Envs: expandAppEnvs(worker["env"].(*schema.Set).List()),
InstanceSizeSlug: worker["instance_size_slug"].(string),
InstanceCount: int64(worker["instance_count"].(int)),
SourceDir: worker["source_dir"].(string),
EnvironmentSlug: worker["environment_slug"].(string),
}
github := worker["github"].([]interface{})
if len(github) > 0 {
s.GitHub = expandAppGitHubSourceSpec(github)
}
git := worker["git"].([]interface{})
if len(git) > 0 {
s.Git = expandAppGitSourceSpec(git)
}
appWorkers = append(appWorkers, s)
}
return appWorkers
}
func flattenAppSpecWorkers(workers []*godo.AppWorkerSpec) []map[string]interface{} {
result := make([]map[string]interface{}, len(workers))
for i, w := range workers {
r := make(map[string]interface{})
r["name"] = w.Name
r["run_command"] = w.RunCommand
r["build_command"] = w.BuildCommand
r["github"] = flattenAppGitHubSourceSpec(w.GitHub)
r["git"] = flattenAppGitSourceSpec(w.Git)
r["dockerfile_path"] = w.DockerfilePath
r["env"] = flattenAppEnvs(w.Envs)
r["instance_size_slug"] = w.InstanceSizeSlug
r["instance_count"] = int(w.InstanceCount)
r["source_dir"] = w.SourceDir
r["environment_slug"] = w.EnvironmentSlug
result[i] = r
}
return result
}
func expandAppSpecDatabases(config []interface{}) []*godo.AppDatabaseSpec {
appDatabases := make([]*godo.AppDatabaseSpec, 0, len(config))
for _, rawDatabase := range config {
db := rawDatabase.(map[string]interface{})
s := &godo.AppDatabaseSpec{
Name: db["name"].(string),
Engine: godo.AppDatabaseSpecEngine(db["engine"].(string)),
Version: db["version"].(string),
Production: db["production"].(bool),
ClusterName: db["cluster_name"].(string),
DBName: db["db_name"].(string),
DBUser: db["db_user"].(string),
}
appDatabases = append(appDatabases, s)
}
return appDatabases
}
func flattenAppSpecDatabases(databases []*godo.AppDatabaseSpec) []map[string]interface{} {
result := make([]map[string]interface{}, len(databases))
for i, db := range databases {
r := make(map[string]interface{})
r["name"] = db.Name
r["engine"] = db.Engine
r["version"] = db.Version
r["production"] = db.Production
r["cluster_name"] = db.ClusterName
r["db_name"] = db.DBName
r["db_user"] = db.DBUser
result[i] = r
}
return result
}

View File

@ -0,0 +1,56 @@
package digitalocean
import (
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
)
func dataSourceDigitalOceanApp() *schema.Resource {
return &schema.Resource{
Read: dataSourceDigitalOceanAppRead,
Schema: map[string]*schema.Schema{
"app_id": {
Type: schema.TypeString,
Required: true,
},
"spec": {
Type: schema.TypeList,
Computed: true,
MaxItems: 1,
Description: "A DigitalOcean App Platform Spec",
Elem: &schema.Resource{
Schema: appSpecSchema(),
},
},
"default_ingress": {
Type: schema.TypeString,
Computed: true,
Description: "The default URL to access the App",
},
"live_url": {
Type: schema.TypeString,
Computed: true,
},
"active_deployment_id": {
Type: schema.TypeString,
Computed: true,
Description: "The ID the App's currently active deployment",
},
"updated_at": {
Type: schema.TypeString,
Computed: true,
Description: "The date and time of when the App was last updated",
},
"created_at": {
Type: schema.TypeString,
Computed: true,
Description: "The date and time of when the App was created",
},
},
}
}
func dataSourceDigitalOceanAppRead(d *schema.ResourceData, meta interface{}) error {
d.SetId(d.Get("app_id").(string))
return resourceDigitalOceanAppRead(d, meta)
}

View File

@ -0,0 +1,83 @@
package digitalocean
import (
"fmt"
"testing"
"github.com/digitalocean/godo"
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
)
func TestAccDataSourceDigitalOceanApp_Basic(t *testing.T) {
var app godo.App
appName := randomTestName()
appCreateConfig := fmt.Sprintf(testAccCheckDigitalOceanAppConfig_basic, appName)
appDataConfig := fmt.Sprintf(testAccCheckDataSourceDigitalOceanAppConfig, appCreateConfig)
updatedAppCreateConfig := fmt.Sprintf(testAccCheckDigitalOceanAppConfig_addService, appName)
updatedAppDataConfig := fmt.Sprintf(testAccCheckDataSourceDigitalOceanAppConfig, updatedAppCreateConfig)
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: appCreateConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckDigitalOceanAppExists("digitalocean_app.foobar", &app),
),
},
{
Config: appDataConfig,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(
"data.digitalocean_app.foobar", "spec.0.name", appName),
resource.TestCheckResourceAttrPair("digitalocean_app.foobar", "default_ingress",
"data.digitalocean_app.foobar", "default_ingress"),
resource.TestCheckResourceAttrPair("digitalocean_app.foobar", "live_url",
"data.digitalocean_app.foobar", "live_url"),
resource.TestCheckResourceAttrPair("digitalocean_app.foobar", "active_deployment_id",
"data.digitalocean_app.foobar", "active_deployment_id"),
resource.TestCheckResourceAttrPair("digitalocean_app.foobar", "updated_at",
"data.digitalocean_app.foobar", "updated_at"),
resource.TestCheckResourceAttrPair("digitalocean_app.foobar", "created_at",
"data.digitalocean_app.foobar", "created_at"),
resource.TestCheckResourceAttr(
"data.digitalocean_app.foobar", "spec.0.service.0.instance_count", "1"),
resource.TestCheckResourceAttr(
"data.digitalocean_app.foobar", "spec.0.service.0.instance_size_slug", "professional-xs"),
resource.TestCheckResourceAttr(
"data.digitalocean_app.foobar", "spec.0.service.0.routes.0.path", "/"),
resource.TestCheckResourceAttr(
"data.digitalocean_app.foobar", "spec.0.service.0.git.0.repo_clone_url",
"https://github.com/digitalocean/sample-golang.git"),
resource.TestCheckResourceAttr(
"data.digitalocean_app.foobar", "spec.0.service.0.git.0.branch", "main"),
),
},
{
Config: updatedAppDataConfig,
},
{
Config: updatedAppDataConfig,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(
"data.digitalocean_app.foobar", "spec.0.service.0.name", "go-service"),
resource.TestCheckResourceAttr(
"data.digitalocean_app.foobar", "spec.0.service.0.routes.0.path", "/go"),
resource.TestCheckResourceAttr(
"data.digitalocean_app.foobar", "spec.0.service.1.name", "python-service"),
resource.TestCheckResourceAttr(
"data.digitalocean_app.foobar", "spec.0.service.1.routes.0.path", "/python"),
),
},
},
})
}
const testAccCheckDataSourceDigitalOceanAppConfig = `
%s
data "digitalocean_app" "foobar" {
app_id = digitalocean_app.foobar.id
}`

View File

@ -0,0 +1,29 @@
package digitalocean
import (
"fmt"
"testing"
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
)
func TestAccDigitalOceanApp_importBasic(t *testing.T) {
resourceName := "digitalocean_app.foobar"
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDigitalOceanCertificateDestroy,
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(testAccCheckDigitalOceanAppConfig_basic, randomTestName()),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

View File

@ -45,6 +45,7 @@ func Provider() terraform.ResourceProvider {
},
DataSourcesMap: map[string]*schema.Resource{
"digitalocean_account": dataSourceDigitalOceanAccount(),
"digitalocean_app": dataSourceDigitalOceanApp(),
"digitalocean_certificate": dataSourceDigitalOceanCertificate(),
"digitalocean_container_registry": dataSourceDigitalOceanContainerRegistry(),
"digitalocean_database_cluster": dataSourceDigitalOceanDatabaseCluster(),
@ -78,6 +79,7 @@ func Provider() terraform.ResourceProvider {
},
ResourcesMap: map[string]*schema.Resource{
"digitalocean_app": resourceDigitalOceanApp(),
"digitalocean_certificate": resourceDigitalOceanCertificate(),
"digitalocean_container_registry": resourceDigitalOceanContainerRegistry(),
"digitalocean_container_registry_docker_credentials": resourceDigitalOceanContainerRegistryDockerCredentials(),

View File

@ -0,0 +1,206 @@
package digitalocean
import (
"context"
"fmt"
"log"
"time"
"github.com/digitalocean/godo"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
)
func resourceDigitalOceanApp() *schema.Resource {
return &schema.Resource{
Create: resourceDigitalOceanAppCreate,
Read: resourceDigitalOceanAppRead,
Update: resourceDigitalOceanAppUpdate,
Delete: resourceDigitalOceanAppDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Schema: map[string]*schema.Schema{
"spec": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Description: "A DigitalOcean App Platform Spec",
Elem: &schema.Resource{
Schema: appSpecSchema(),
},
},
// Computed attributes
"default_ingress": {
Type: schema.TypeString,
Computed: true,
Description: "The default URL to access the App",
},
"live_url": {
Type: schema.TypeString,
Computed: true,
},
// TODO: The full Deployment should be a data source, not a resource
// specify the app id for the active deployment, include a deployment
// id for a specific one
"active_deployment_id": {
Type: schema.TypeString,
Computed: true,
Description: "The ID the App's currently active deployment",
},
"updated_at": {
Type: schema.TypeString,
Computed: true,
Description: "The date and time of when the App was last updated",
},
"created_at": {
Type: schema.TypeString,
Computed: true,
Description: "The date and time of when the App was created",
},
},
}
}
func resourceDigitalOceanAppCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*CombinedConfig).godoClient()
appCreateRequest := &godo.AppCreateRequest{}
appCreateRequest.Spec = expandAppSpec(d.Get("spec").([]interface{}))
log.Printf("[DEBUG] App create request: %#v", appCreateRequest)
app, _, err := client.Apps.Create(context.Background(), appCreateRequest)
if err != nil {
return fmt.Errorf("Error creating App: %s", err)
}
d.SetId(app.ID)
log.Printf("[DEBUG] Waiting for app (%s) deployment to become active", app.ID)
err = waitForAppDeployment(client, app.ID)
if err != nil {
return err
}
log.Printf("[INFO] App created, ID: %s", d.Id())
return resourceDigitalOceanAppRead(d, meta)
}
func resourceDigitalOceanAppRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*CombinedConfig).godoClient()
app, resp, err := client.Apps.Get(context.Background(), d.Id())
if err != nil {
if resp != nil && resp.StatusCode == 404 {
log.Printf("[DEBUG] App (%s) was not found - removing from state", d.Id())
d.SetId("")
return nil
}
return fmt.Errorf("Error reading App: %s", err)
}
d.SetId(app.ID)
d.Set("default_ingress", app.DefaultIngress)
d.Set("live_url", app.LiveURL)
d.Set("active_deployment_id", app.ActiveDeployment.ID)
d.Set("updated_at", app.UpdatedAt.UTC().String())
d.Set("created_at", app.CreatedAt.UTC().String())
if err := d.Set("spec", flattenAppSpec(app.Spec)); err != nil {
return fmt.Errorf("[DEBUG] Error setting app spec: %#v", err)
}
return err
}
func resourceDigitalOceanAppUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*CombinedConfig).godoClient()
if d.HasChange("spec") {
appUpdateRequest := &godo.AppUpdateRequest{}
appUpdateRequest.Spec = expandAppSpec(d.Get("spec").([]interface{}))
app, _, err := client.Apps.Update(context.Background(), d.Id(), appUpdateRequest)
if err != nil {
return fmt.Errorf("Error updating app (%s): %s", d.Id(), err)
}
log.Printf("[DEBUG] Waiting for app (%s) deployment to become active", app.ID)
err = waitForAppDeployment(client, app.ID)
if err != nil {
return err
}
log.Printf("[INFO] Updated app (%s)", app.ID)
}
return resourceDigitalOceanAppRead(d, meta)
}
func resourceDigitalOceanAppDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*CombinedConfig).godoClient()
log.Printf("[INFO] Deleting App: %s", d.Id())
_, err := client.Apps.Delete(context.Background(), d.Id())
if err != nil {
return fmt.Errorf("Error deletingApp: %s", err)
}
d.SetId("")
return nil
}
func waitForAppDeployment(client *godo.Client, id string) error {
tickerInterval := 10 //10s
timeout := 1800 //1800s, 30min
n := 0
var deploymentID string
ticker := time.NewTicker(time.Duration(tickerInterval) * time.Second)
for range ticker.C {
if n*tickerInterval > timeout {
ticker.Stop()
break
}
if deploymentID == "" {
app, _, err := client.Apps.Get(context.Background(), id)
if err != nil {
return fmt.Errorf("Error trying to read app deployment state: %s", err)
}
if app.InProgressDeployment != nil {
deploymentID = app.InProgressDeployment.ID
}
} else {
deployment, _, err := client.Apps.GetDeployment(context.Background(), id, deploymentID)
if err != nil {
ticker.Stop()
return fmt.Errorf("Error trying to read app deployment state: %s", err)
}
allSuccessful := deployment.Progress.SuccessSteps == deployment.Progress.TotalSteps
if allSuccessful {
ticker.Stop()
return nil
}
if deployment.Progress.ErrorSteps > 0 {
ticker.Stop()
return fmt.Errorf("error deploying app (%s) (deployment ID: %s):\n%s", id, deployment.ID, godo.Stringify(deployment.Progress))
}
log.Printf("[DEBUG] Waiting for app (%s) deployment (%s) to become active. Phase: %s (%d/%d)",
id, deployment.ID, deployment.Phase, deployment.Progress.SuccessSteps, deployment.Progress.TotalSteps)
}
n++
}
return fmt.Errorf("timeout waiting to app (%s) deployment", id)
}

View File

@ -0,0 +1,492 @@
package digitalocean
import (
"context"
"fmt"
"log"
"strings"
"testing"
"github.com/digitalocean/godo"
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/terraform"
)
func init() {
resource.AddTestSweepers("digitalocean_app", &resource.Sweeper{
Name: "digitalocean_app",
F: testSweepApp,
})
}
func testSweepApp(region string) error {
meta, err := sharedConfigForRegion(region)
if err != nil {
return err
}
client := meta.(*CombinedConfig).godoClient()
opt := &godo.ListOptions{PerPage: 200}
apps, _, err := client.Apps.List(context.Background(), opt)
if err != nil {
return err
}
for _, app := range apps {
if strings.HasPrefix(app.Spec.Name, testNamePrefix) {
log.Printf("Destroying app %s", app.Spec.Name)
if _, err := client.Apps.Delete(context.Background(), app.ID); err != nil {
return err
}
}
}
return nil
}
func TestAccDigitalOceanApp_Basic(t *testing.T) {
var app godo.App
appName := randomTestName()
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDigitalOceanAppDestroy,
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(testAccCheckDigitalOceanAppConfig_basic, appName),
Check: resource.ComposeTestCheckFunc(
testAccCheckDigitalOceanAppExists("digitalocean_app.foobar", &app),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.name", appName),
resource.TestCheckResourceAttrSet("digitalocean_app.foobar", "default_ingress"),
resource.TestCheckResourceAttrSet("digitalocean_app.foobar", "live_url"),
resource.TestCheckResourceAttrSet("digitalocean_app.foobar", "active_deployment_id"),
resource.TestCheckResourceAttrSet("digitalocean_app.foobar", "updated_at"),
resource.TestCheckResourceAttrSet("digitalocean_app.foobar", "created_at"),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.service.0.instance_count", "1"),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.service.0.instance_size_slug", "basic-xxs"),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.service.0.routes.0.path", "/"),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.service.0.git.0.repo_clone_url",
"https://github.com/digitalocean/sample-golang.git"),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.service.0.git.0.branch", "main"),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.service.0.health_check.0.http_path", "/"),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.service.0.health_check.0.timeout_seconds", "10"),
),
},
{
Config: fmt.Sprintf(testAccCheckDigitalOceanAppConfig_addService, appName),
Check: resource.ComposeTestCheckFunc(
testAccCheckDigitalOceanAppExists("digitalocean_app.foobar", &app),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.service.0.name", "go-service"),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.service.0.routes.0.path", "/go"),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.service.1.name", "python-service"),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.service.1.routes.0.path", "/python"),
),
},
{
Config: fmt.Sprintf(testAccCheckDigitalOceanAppConfig_addDatabase, appName),
Check: resource.ComposeTestCheckFunc(
testAccCheckDigitalOceanAppExists("digitalocean_app.foobar", &app),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.service.0.routes.0.path", "/"),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.database.0.name", "test-db"),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.database.0.engine", "PG"),
),
},
},
})
}
func TestAccDigitalOceanApp_StaticSite(t *testing.T) {
var app godo.App
appName := randomTestName()
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDigitalOceanAppDestroy,
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(testAccCheckDigitalOceanAppConfig_StaticSite, appName),
Check: resource.ComposeTestCheckFunc(
testAccCheckDigitalOceanAppExists("digitalocean_app.foobar", &app),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.name", appName),
resource.TestCheckResourceAttrSet("digitalocean_app.foobar", "default_ingress"),
resource.TestCheckResourceAttrSet("digitalocean_app.foobar", "live_url"),
resource.TestCheckResourceAttrSet("digitalocean_app.foobar", "active_deployment_id"),
resource.TestCheckResourceAttrSet("digitalocean_app.foobar", "updated_at"),
resource.TestCheckResourceAttrSet("digitalocean_app.foobar", "created_at"),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.static_site.0.routes.0.path", "/"),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.static_site.0.build_command", "bundle exec jekyll build -d ./public"),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.static_site.0.output_dir", "/public"),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.static_site.0.git.0.repo_clone_url",
"https://github.com/digitalocean/sample-jekyll.git"),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.static_site.0.git.0.branch", "main"),
),
},
},
})
}
func TestAccDigitalOceanApp_Envs(t *testing.T) {
var app godo.App
appName := randomTestName()
oneEnv := `
env {
key = "FOO"
value = "bar"
}
`
twoEnvs := `
env {
key = "FOO"
value = "bar"
}
env {
key = "FIZZ"
value = "pop"
scope = "BUILD_TIME"
}
`
oneEnvUpdated := `
env {
key = "FOO"
value = "baz"
scope = "RUN_TIME"
}
`
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDigitalOceanAppDestroy,
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(testAccCheckDigitalOceanAppConfig_Envs, appName, oneEnv),
Check: resource.ComposeTestCheckFunc(
testAccCheckDigitalOceanAppExists("digitalocean_app.foobar", &app),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.name", appName),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.service.0.env.#", "1"),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.service.0.env.3118534296.key", "FOO"),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.service.0.env.3118534296.value", "bar"),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.service.0.env.3118534296.scope", "RUN_AND_BUILD_TIME"),
),
},
{
Config: fmt.Sprintf(testAccCheckDigitalOceanAppConfig_Envs, appName, twoEnvs),
Check: resource.ComposeTestCheckFunc(
testAccCheckDigitalOceanAppExists("digitalocean_app.foobar", &app),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.name", appName),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.service.0.env.#", "2"),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.service.0.env.3118534296.key", "FOO"),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.service.0.env.3118534296.value", "bar"),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.service.0.env.3118534296.scope", "RUN_AND_BUILD_TIME"),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.service.0.env.1776096292.key", "FIZZ"),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.service.0.env.1776096292.value", "pop"),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.service.0.env.1776096292.scope", "BUILD_TIME"),
),
},
{
Config: fmt.Sprintf(testAccCheckDigitalOceanAppConfig_Envs, appName, oneEnvUpdated),
Check: resource.ComposeTestCheckFunc(
testAccCheckDigitalOceanAppExists("digitalocean_app.foobar", &app),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.name", appName),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.service.0.env.#", "1"),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.service.0.env.1277866902.key", "FOO"),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.service.0.env.1277866902.value", "baz"),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.service.0.env.1277866902.scope", "RUN_TIME"),
),
},
},
})
}
func TestAccDigitalOceanApp_Worker(t *testing.T) {
var app godo.App
appName := randomTestName()
workerConfig := fmt.Sprintf(testAccCheckDigitalOceanAppConfig_worker, appName, "basic-xxs")
upgradedWorkerConfig := fmt.Sprintf(testAccCheckDigitalOceanAppConfig_worker, appName, "professional-xs")
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDigitalOceanAppDestroy,
Steps: []resource.TestStep{
{
Config: workerConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckDigitalOceanAppExists("digitalocean_app.foobar", &app),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.name", appName),
resource.TestCheckResourceAttrSet("digitalocean_app.foobar", "active_deployment_id"),
resource.TestCheckResourceAttrSet("digitalocean_app.foobar", "updated_at"),
resource.TestCheckResourceAttrSet("digitalocean_app.foobar", "created_at"),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.worker.0.instance_count", "1"),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.worker.0.instance_size_slug", "basic-xxs"),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.worker.0.git.0.repo_clone_url",
"https://github.com/digitalocean/sample-sleeper.git"),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.worker.0.git.0.branch", "main"),
),
},
{
Config: upgradedWorkerConfig,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.worker.0.instance_size_slug", "professional-xs"),
),
},
},
})
}
func testAccCheckDigitalOceanAppDestroy(s *terraform.State) error {
client := testAccProvider.Meta().(*CombinedConfig).godoClient()
for _, rs := range s.RootModule().Resources {
if rs.Type != "digitalocean_app" {
continue
}
_, _, err := client.Apps.Get(context.Background(), rs.Primary.ID)
if err == nil {
return fmt.Errorf("Container Registry still exists")
}
}
return nil
}
func testAccCheckDigitalOceanAppExists(n string, app *godo.App) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
if rs.Primary.ID == "" {
return fmt.Errorf("No Record ID is set")
}
client := testAccProvider.Meta().(*CombinedConfig).godoClient()
foundApp, _, err := client.Apps.Get(context.Background(), rs.Primary.ID)
if err != nil {
return err
}
*app = *foundApp
return nil
}
}
var testAccCheckDigitalOceanAppConfig_basic = `
resource "digitalocean_app" "foobar" {
spec {
name = "%s"
region = "ams"
service {
name = "go-service"
environment_slug = "go"
instance_count = 1
instance_size_slug = "basic-xxs"
git {
repo_clone_url = "https://github.com/digitalocean/sample-golang.git"
branch = "main"
}
health_check {
http_path = "/"
timeout_seconds = 10
}
}
}
}`
var testAccCheckDigitalOceanAppConfig_addService = `
resource "digitalocean_app" "foobar" {
spec {
name = "%s"
region = "ams"
service {
name = "go-service"
environment_slug = "go"
instance_count = 1
instance_size_slug = "basic-xxs"
git {
repo_clone_url = "https://github.com/digitalocean/sample-golang.git"
branch = "main"
}
routes {
path = "/go"
}
}
service {
name = "python-service"
environment_slug = "python"
instance_count = 1
instance_size_slug = "basic-xxs"
git {
repo_clone_url = "https://github.com/digitalocean/sample-python.git"
branch = "main"
}
routes {
path = "/python"
}
}
}
}`
var testAccCheckDigitalOceanAppConfig_addDatabase = `
resource "digitalocean_app" "foobar" {
spec {
name = "%s"
region = "ams"
service {
name = "go-service"
environment_slug = "go"
instance_count = 1
instance_size_slug = "basic-xxs"
git {
repo_clone_url = "https://github.com/digitalocean/sample-golang.git"
branch = "main"
}
routes {
path = "/"
}
}
database {
name = "test-db"
engine = "PG"
production = false
}
}
}`
var testAccCheckDigitalOceanAppConfig_StaticSite = `
resource "digitalocean_app" "foobar" {
spec {
name = "%s"
region = "ams"
static_site {
name = "sample-jekyll"
build_command = "bundle exec jekyll build -d ./public"
output_dir = "/public"
environment_slug = "jekyll"
git {
repo_clone_url = "https://github.com/digitalocean/sample-jekyll.git"
branch = "main"
}
routes {
path = "/"
}
}
}
}`
var testAccCheckDigitalOceanAppConfig_Envs = `
resource "digitalocean_app" "foobar" {
spec {
name = "%s"
region = "ams"
service {
name = "go-service"
environment_slug = "go"
instance_count = 1
instance_size_slug = "basic-xxs"
git {
repo_clone_url = "https://github.com/digitalocean/sample-golang.git"
branch = "main"
}
%s
}
}
}`
var testAccCheckDigitalOceanAppConfig_worker = `
resource "digitalocean_app" "foobar" {
spec {
name = "%s"
region = "ams"
worker {
name = "go-worker"
instance_count = 1
instance_size_slug = "%s"
git {
repo_clone_url = "https://github.com/digitalocean/sample-sleeper.git"
branch = "main"
}
}
}
}`

3
go.mod
View File

@ -3,7 +3,7 @@ module github.com/digitalocean/terraform-provider-digitalocean
require (
contrib.go.opencensus.io/exporter/ocagent v0.6.0 // indirect
github.com/aws/aws-sdk-go v1.25.4
github.com/digitalocean/godo v1.42.1
github.com/digitalocean/godo v1.45.0
github.com/hashicorp/go-version v1.2.0
github.com/hashicorp/terraform-plugin-sdk v1.15.0
github.com/mitchellh/go-homedir v1.1.0
@ -12,6 +12,7 @@ require (
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
gopkg.in/yaml.v2 v2.2.4
sigs.k8s.io/yaml v1.1.0
)
replace git.apache.org/thrift.git => github.com/apache/thrift v0.0.0-20180902110319-2566ecd5d999

5
go.sum
View File

@ -87,8 +87,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/digitalocean/godo v1.42.1 h1:SJ/XMVsp5CZmyQal8gLlOl9jSl1i3FaN20LlgtK5ZMs=
github.com/digitalocean/godo v1.42.1/go.mod h1:p7dOjjtSBqCTUksqtA5Fd3uaKs9kyTq2xcz76ulEJRU=
github.com/digitalocean/godo v1.45.0 h1:Hg4Q216Xr0AJjnAxK4bkP/qacj4svGaapWRfC8z9URc=
github.com/digitalocean/godo v1.45.0/go.mod h1:p7dOjjtSBqCTUksqtA5Fd3uaKs9kyTq2xcz76ulEJRU=
github.com/dustinkirkland/golang-petname v0.0.0-20170105215008-242afa0b4f8a/go.mod h1:V+Qd57rJe8gd4eiGzZyg4h54VLHmYVVw54iMnlAMrF8=
github.com/dustinkirkland/golang-petname v0.0.0-20170921220637-d3c2ba80e75e h1:bRcq7ruHMqCVB/ugLbBylx+LrccNACFDEaqAD/aZ80Q=
github.com/dustinkirkland/golang-petname v0.0.0-20170921220637-d3c2ba80e75e/go.mod h1:V+Qd57rJe8gd4eiGzZyg4h54VLHmYVVw54iMnlAMrF8=
@ -274,6 +274,7 @@ github.com/hashicorp/terraform-plugin-sdk v1.1.1 h1:wQ2HtvOE8K4QYcm2JB8YFw9u7Opl
github.com/hashicorp/terraform-plugin-sdk v1.1.1/go.mod h1:NuwtLpEpPsFaKJPJNGtMcn9vlhe6Ofe+Y6NqXhJgV2M=
github.com/hashicorp/terraform-plugin-sdk v1.15.0 h1:bmYnTT7MqNXlUHDc7pT8E6uKT2g/upjlRLypJFK1OQU=
github.com/hashicorp/terraform-plugin-sdk v1.15.0/go.mod h1:PuFTln8urDmRM6mV0II6apOTsyG/iHkxp+5W11eJE58=
github.com/hashicorp/terraform-plugin-sdk v1.16.0 h1:NrkXMRjHErUPPTHQkZ6JIn6bByiJzGnlJzH1rVdNEuE=
github.com/hashicorp/terraform-plugin-test v1.4.3 h1:HSOZZu2W7a9tx4QPYXhrT9oh7JptibaSkP7CK4C0OF0=
github.com/hashicorp/terraform-plugin-test v1.4.3/go.mod h1:UA7z/02pgqsRLut4DJIPm0Hjnj27uOvhi19c8kTqIfM=
github.com/hashicorp/terraform-svchost v0.0.0-20191011084731-65d371908596 h1:hjyO2JsNZUKT1ym+FAdlBEkGPevazYsmVgIMw7dVELg=

View File

@ -1,5 +1,21 @@
# Change Log
## [v1.45.0] - 2020-09-25
**Note**: This release contains breaking changes to App Platform features currently in closed beta.
- #369 update apps types to latest - @kamaln7
- #368 Kubernetes: add taints field to node pool create and update requests - @timoreimann
- #367 update apps types, address marshaling bug - @kamaln7
## [v1.44.0] - 2020-09-08
- #364 apps: support aggregate deployment logs - @kamaln7
## [v1.43.0] - 2020-09-08
- #362 update apps types - @kamaln7
## [v1.42.1] - 2020-08-06
- #360 domains: Allow for SRV records with port 0. - @andrewsomething

View File

@ -3,15 +3,43 @@
package godo
import ()
import (
"time"
)
// App An application's configuration and status.
type App struct {
ID string `json:"id,omitempty"`
OwnerUUID string `json:"owner_uuid,omitempty"`
Spec *AppSpec `json:"spec"`
DefaultIngress string `json:"default_ingress,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
ActiveDeployment *Deployment `json:"active_deployment,omitempty"`
InProgressDeployment *Deployment `json:"in_progress_deployment,omitempty"`
LastDeploymentCreatedAt time.Time `json:"last_deployment_created_at,omitempty"`
LiveURL string `json:"live_url,omitempty"`
Region *AppRegion `json:"region,omitempty"`
}
// AppDatabaseSpec struct for AppDatabaseSpec
type AppDatabaseSpec struct {
Name string `json:"name"`
Engine AppDatabaseSpecEngine `json:"engine,omitempty"`
Version string `json:"version,omitempty"`
Size string `json:"size,omitempty"`
NumNodes int64 `json:"num_nodes,omitempty"`
// The name. Must be unique across all components within the same app.
Name string `json:"name"`
Engine AppDatabaseSpecEngine `json:"engine,omitempty"`
Version string `json:"version,omitempty"`
// Deprecated.
Size string `json:"size,omitempty"`
// Deprecated.
NumNodes int64 `json:"num_nodes,omitempty"`
// Whether this is a production or dev database.
Production bool `json:"production,omitempty"`
// The name of the underlying DigitalOcean DBaaS cluster. This is required for production databases. For dev databases, if cluster_name is not set, a new cluster will be provisioned.
ClusterName string `json:"cluster_name,omitempty"`
// The name of the MySQL or PostgreSQL database to configure.
DBName string `json:"db_name,omitempty"`
// The name of the MySQL or PostgreSQL user to configure.
DBUser string `json:"db_user,omitempty"`
}
// AppDatabaseSpecEngine the model 'AppDatabaseSpecEngine'
@ -19,118 +47,322 @@ type AppDatabaseSpecEngine string
// List of AppDatabaseSpecEngine
const (
APPDATABASESPECENGINE_UNSET AppDatabaseSpecEngine = "UNSET"
APPDATABASESPECENGINE_MYSQL AppDatabaseSpecEngine = "MYSQL"
APPDATABASESPECENGINE_PG AppDatabaseSpecEngine = "PG"
APPDATABASESPECENGINE_REDIS AppDatabaseSpecEngine = "REDIS"
AppDatabaseSpecEngine_Unset AppDatabaseSpecEngine = "UNSET"
AppDatabaseSpecEngine_MySQL AppDatabaseSpecEngine = "MYSQL"
AppDatabaseSpecEngine_PG AppDatabaseSpecEngine = "PG"
AppDatabaseSpecEngine_Redis AppDatabaseSpecEngine = "REDIS"
)
// AppDomainSpec struct for AppDomainSpec
type AppDomainSpec struct {
Domain string `json:"domain"`
// The hostname.
Domain string `json:"domain"`
Type AppDomainSpecType `json:"type,omitempty"`
}
// AppDomainSpecType - DEFAULT: The default .ondigitalocean.app domain assigned to this app. - PRIMARY: The primary domain for this app. This is the domain that is displayed as the default in the control panel, used in bindable environment variables, and any other places that reference an app's live URL. Only one domain may be set as primary. - ALIAS: A non-primary domain.
type AppDomainSpecType string
// List of AppDomainSpecType
const (
AppDomainSpecType_Unspecified AppDomainSpecType = "UNSPECIFIED"
AppDomainSpecType_Default AppDomainSpecType = "DEFAULT"
AppDomainSpecType_Primary AppDomainSpecType = "PRIMARY"
AppDomainSpecType_Alias AppDomainSpecType = "ALIAS"
)
// AppJobSpec struct for AppJobSpec
type AppJobSpec struct {
// The name. Must be unique across all components within the same app.
Name string `json:"name"`
Git *GitSourceSpec `json:"git,omitempty"`
GitHub *GitHubSourceSpec `json:"github,omitempty"`
// The path to the Dockerfile relative to the root of the repo. If set, it will be used to build this component. Otherwise, App Platform will attempt to build it using buildpacks.
DockerfilePath string `json:"dockerfile_path,omitempty"`
// An optional build command to run while building this component from source.
BuildCommand string `json:"build_command,omitempty"`
// An optional run command to override the component's default.
RunCommand string `json:"run_command,omitempty"`
// An optional path to the working directory to use for the build. For Dockerfile builds, this will be used as the build context. Must be relative to the root of the repo.
SourceDir string `json:"source_dir,omitempty"`
// An environment slug describing the type of this app. For a full list, please refer to [the product documentation](https://www.digitalocean.com/docs/app-platform/).
EnvironmentSlug string `json:"environment_slug,omitempty"`
// A list of environment variables made available to the component.
Envs []*AppVariableDefinition `json:"envs,omitempty"`
// The instance size to use for this component.
InstanceSizeSlug string `json:"instance_size_slug,omitempty"`
InstanceCount int64 `json:"instance_count,omitempty"`
}
// AppRouteSpec struct for AppRouteSpec
type AppRouteSpec struct {
// An HTTP path prefix. Paths must start with / and must be unique across all components within an app.
Path string `json:"path,omitempty"`
}
// AppServiceSpec struct for AppServiceSpec
type AppServiceSpec struct {
Name string `json:"name"`
RunCommand string `json:"run_command,omitempty"`
BuildCommand string `json:"build_command,omitempty"`
HTTPPort int64 `json:"http_port,omitempty"`
DockerfilePath string `json:"dockerfile_path,omitempty"`
Git GitSourceSpec `json:"git,omitempty"`
GitHub GitHubSourceSpec `json:"github,omitempty"`
Envs []AppVariableDefinition `json:"envs,omitempty"`
InstanceSizeSlug string `json:"instance_size_slug,omitempty"`
InstanceCount int64 `json:"instance_count,omitempty"`
Routes []AppRouteSpec `json:"routes,omitempty"`
SourceDir string `json:"source_dir,omitempty"`
EnvironmentSlug string `json:"environment_slug,omitempty"`
// The name. Must be unique across all components within the same app.
Name string `json:"name"`
Git *GitSourceSpec `json:"git,omitempty"`
GitHub *GitHubSourceSpec `json:"github,omitempty"`
// The path to the Dockerfile relative to the root of the repo. If set, it will be used to build this component. Otherwise, App Platform will attempt to build it using buildpacks.
DockerfilePath string `json:"dockerfile_path,omitempty"`
// An optional build command to run while building this component from source.
BuildCommand string `json:"build_command,omitempty"`
// An optional run command to override the component's default.
RunCommand string `json:"run_command,omitempty"`
// An optional path to the working directory to use for the build. For Dockerfile builds, this will be used as the build context. Must be relative to the root of the repo.
SourceDir string `json:"source_dir,omitempty"`
// An environment slug describing the type of this app. For a full list, please refer to [the product documentation](https://www.digitalocean.com/docs/app-platform/).
EnvironmentSlug string `json:"environment_slug,omitempty"`
// A list of environment variables made available to the component.
Envs []*AppVariableDefinition `json:"envs,omitempty"`
// The instance size to use for this component.
InstanceSizeSlug string `json:"instance_size_slug,omitempty"`
InstanceCount int64 `json:"instance_count,omitempty"`
// The internal port on which this service's run command will listen. Default: 8080 If there is not an environment variable with the name `PORT`, one will be automatically added with its value set to the value of this field.
HTTPPort int64 `json:"http_port,omitempty"`
// A list of HTTP routes that should be routed to this component.
Routes []*AppRouteSpec `json:"routes,omitempty"`
HealthCheck *AppServiceSpecHealthCheck `json:"health_check,omitempty"`
}
// AppSpec struct for AppSpec
// AppServiceSpecHealthCheck struct for AppServiceSpecHealthCheck
type AppServiceSpecHealthCheck struct {
// Deprecated. Use http_path instead.
Path string `json:"path,omitempty"`
// The number of seconds to wait before beginning health checks.
InitialDelaySeconds int32 `json:"initial_delay_seconds,omitempty"`
// The number of seconds to wait between health checks.
PeriodSeconds int32 `json:"period_seconds,omitempty"`
// The number of seconds after which the check times out.
TimeoutSeconds int32 `json:"timeout_seconds,omitempty"`
// The number of successful health checks before considered healthy.
SuccessThreshold int32 `json:"success_threshold,omitempty"`
// The number of failed health checks before considered unhealthy.
FailureThreshold int32 `json:"failure_threshold,omitempty"`
// The route path used for the HTTP health check ping. If not set, the HTTP health check will be disabled and a TCP health check used instead.
HTTPPath string `json:"http_path,omitempty"`
}
// AppSpec The desired configuration of an application.
type AppSpec struct {
Services []AppServiceSpec `json:"services,omitempty"`
StaticSites []AppStaticSiteSpec `json:"static_sites,omitempty"`
Databases []AppDatabaseSpec `json:"databases,omitempty"`
Workers []AppWorkerSpec `json:"workers,omitempty"`
Region string `json:"region,omitempty"`
Name string `json:"name"`
Domains []AppDomainSpec `json:"domains,omitempty"`
// The name of the app. Must be unique across all in the same account.
Name string `json:"name"`
// Workloads which expose publicy-accessible HTTP services.
Services []*AppServiceSpec `json:"services,omitempty"`
// Content which can be rendered to static web assets.
StaticSites []*AppStaticSiteSpec `json:"static_sites,omitempty"`
// Workloads which do not expose publicly-accessible HTTP services.
Workers []*AppWorkerSpec `json:"workers,omitempty"`
// One-time or recurring workloads which do not expose publicly-accessible HTTP routes.
Jobs []*AppJobSpec `json:"jobs,omitempty"`
// Database instances which can provide persistence to workloads within the application.
Databases []*AppDatabaseSpec `json:"databases,omitempty"`
// A set of hostnames where the application will be available.
Domains []*AppDomainSpec `json:"domains,omitempty"`
// The slug form of the geographical origin of the app.
Region string `json:"region,omitempty"`
}
// AppStaticSiteSpec struct for AppStaticSiteSpec
type AppStaticSiteSpec struct {
Name string `json:"name"`
BuildCommand string `json:"build_command,omitempty"`
Git GitSourceSpec `json:"git,omitempty"`
GitHub GitHubSourceSpec `json:"github,omitempty"`
Envs []AppVariableDefinition `json:"envs,omitempty"`
Routes []AppRouteSpec `json:"routes,omitempty"`
SourceDir string `json:"source_dir,omitempty"`
EnvironmentSlug string `json:"environment_slug,omitempty"`
// The name. Must be unique across all components within the same app.
Name string `json:"name"`
Git *GitSourceSpec `json:"git,omitempty"`
GitHub *GitHubSourceSpec `json:"github,omitempty"`
// The path to the Dockerfile relative to the root of the repo. If set, it will be used to build this component. Otherwise, App Platform will attempt to build it using buildpacks.
DockerfilePath string `json:"dockerfile_path,omitempty"`
// An optional build command to run while building this component from source.
BuildCommand string `json:"build_command,omitempty"`
// An optional path to the working directory to use for the build. For Dockerfile builds, this will be used as the build context. Must be relative to the root of the repo.
SourceDir string `json:"source_dir,omitempty"`
// An environment slug describing the type of this app. For a full list, please refer to [the product documentation](https://www.digitalocean.com/docs/app-platform/).
EnvironmentSlug string `json:"environment_slug,omitempty"`
// An optional path to where the built assets will be located, relative to the build context. If not set, App Platform will automatically scan for these directory names: `_static`, `dist`, `public`.
OutputDir string `json:"output_dir,omitempty"`
IndexDocument string `json:"index_document,omitempty"`
// The name of the error document to use when serving this static site. Default: 404.html. If no such file exists within the built assets, App Platform will supply one.
ErrorDocument string `json:"error_document,omitempty"`
// A list of environment variables made available to the component.
Envs []*AppVariableDefinition `json:"envs,omitempty"`
// A list of HTTP routes that should be routed to this component.
Routes []*AppRouteSpec `json:"routes,omitempty"`
}
// AppVariableDefinition struct for AppVariableDefinition
type AppVariableDefinition struct {
// The name
Key string `json:"key"`
// The value. If the type is SECRET, the value will be encrypted on first submission. On following submissions, the encrypted value must be used.
Value string `json:"value,omitempty"`
Scope VariableScope `json:"scope,omitempty"`
// POSIX allows a broader env var definition, but we restrict to what is allowed by bash. http://git.savannah.gnu.org/cgit/bash.git/tree/general.h?h=bash-5.0#n124 Based on the POSIX spec and some casting to unsigned char in bash code I think this is restricted to ASCII (not unicode).
Key string `json:"key"`
Type VariableType `json:"type,omitempty"`
EncryptedValue string `json:"encrypted_value,omitempty"`
Scope AppVariableScope `json:"scope,omitempty"`
Type AppVariableType `json:"type,omitempty"`
}
// AppWorkerSpec struct for AppWorkerSpec
type AppWorkerSpec struct {
Name string `json:"name"`
RunCommand string `json:"run_command,omitempty"`
BuildCommand string `json:"build_command,omitempty"`
DockerfilePath string `json:"dockerfile_path,omitempty"`
Git GitSourceSpec `json:"git,omitempty"`
GitHub GitHubSourceSpec `json:"github,omitempty"`
Envs []AppVariableDefinition `json:"envs,omitempty"`
InstanceSizeSlug string `json:"instance_size_slug,omitempty"`
InstanceCount int64 `json:"instance_count,omitempty"`
SourceDir string `json:"source_dir,omitempty"`
EnvironmentSlug string `json:"environment_slug,omitempty"`
// The name. Must be unique across all components within the same app.
Name string `json:"name"`
Git *GitSourceSpec `json:"git,omitempty"`
GitHub *GitHubSourceSpec `json:"github,omitempty"`
// The path to the Dockerfile relative to the root of the repo. If set, it will be used to build this component. Otherwise, App Platform will attempt to build it using buildpacks.
DockerfilePath string `json:"dockerfile_path,omitempty"`
// An optional build command to run while building this component from source.
BuildCommand string `json:"build_command,omitempty"`
// An optional run command to override the component's default.
RunCommand string `json:"run_command,omitempty"`
// An optional path to the working directory to use for the build. For Dockerfile builds, this will be used as the build context. Must be relative to the root of the repo.
SourceDir string `json:"source_dir,omitempty"`
// An environment slug describing the type of this app. For a full list, please refer to [the product documentation](https://www.digitalocean.com/docs/app-platform/).
EnvironmentSlug string `json:"environment_slug,omitempty"`
// A list of environment variables made available to the component.
Envs []*AppVariableDefinition `json:"envs,omitempty"`
// The instance size to use for this component.
InstanceSizeSlug string `json:"instance_size_slug,omitempty"`
InstanceCount int64 `json:"instance_count,omitempty"`
}
// Deployment struct for Deployment
type Deployment struct {
ID string `json:"id,omitempty"`
Spec *AppSpec `json:"spec,omitempty"`
Services []*DeploymentService `json:"services,omitempty"`
StaticSites []*DeploymentStaticSite `json:"static_sites,omitempty"`
Workers []*DeploymentWorker `json:"workers,omitempty"`
Jobs []*DeploymentJob `json:"jobs,omitempty"`
PhaseLastUpdatedAt time.Time `json:"phase_last_updated_at,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
Cause string `json:"cause,omitempty"`
ClonedFrom string `json:"cloned_from,omitempty"`
Progress *DeploymentProgress `json:"progress,omitempty"`
Phase DeploymentPhase `json:"phase,omitempty"`
}
// DeploymentJob struct for DeploymentJob
type DeploymentJob struct {
Name string `json:"name,omitempty"`
SourceCommitHash string `json:"source_commit_hash,omitempty"`
}
// DeploymentPhase the model 'DeploymentPhase'
type DeploymentPhase string
// List of DeploymentPhase
const (
DeploymentPhase_Unknown DeploymentPhase = "UNKNOWN"
DeploymentPhase_PendingBuild DeploymentPhase = "PENDING_BUILD"
DeploymentPhase_Building DeploymentPhase = "BUILDING"
DeploymentPhase_PendingDeploy DeploymentPhase = "PENDING_DEPLOY"
DeploymentPhase_Deploying DeploymentPhase = "DEPLOYING"
DeploymentPhase_Active DeploymentPhase = "ACTIVE"
DeploymentPhase_Superseded DeploymentPhase = "SUPERSEDED"
DeploymentPhase_Error DeploymentPhase = "ERROR"
DeploymentPhase_Canceled DeploymentPhase = "CANCELED"
)
// DeploymentProgress struct for DeploymentProgress
type DeploymentProgress struct {
PendingSteps int32 `json:"pending_steps,omitempty"`
RunningSteps int32 `json:"running_steps,omitempty"`
SuccessSteps int32 `json:"success_steps,omitempty"`
ErrorSteps int32 `json:"error_steps,omitempty"`
TotalSteps int32 `json:"total_steps,omitempty"`
Steps []*DeploymentProgressStep `json:"steps,omitempty"`
SummarySteps []*DeploymentProgressStep `json:"summary_steps,omitempty"`
}
// DeploymentService struct for DeploymentService
type DeploymentService struct {
Name string `json:"name,omitempty"`
SourceCommitHash string `json:"source_commit_hash,omitempty"`
}
// DeploymentStaticSite struct for DeploymentStaticSite
type DeploymentStaticSite struct {
Name string `json:"name,omitempty"`
SourceCommitHash string `json:"source_commit_hash,omitempty"`
}
// DeploymentWorker struct for DeploymentWorker
type DeploymentWorker struct {
Name string `json:"name,omitempty"`
SourceCommitHash string `json:"source_commit_hash,omitempty"`
}
// GitHubSourceSpec struct for GitHubSourceSpec
type GitHubSourceSpec struct {
Repo string `json:"repo"`
Branch string `json:"branch"`
Repo string `json:"repo,omitempty"`
Branch string `json:"branch,omitempty"`
DeployOnPush bool `json:"deploy_on_push,omitempty"`
}
// GitSourceSpec struct for GitSourceSpec
type GitSourceSpec struct {
Repo string `json:"repo,omitempty"`
RequiresAuth bool `json:"requires_auth,omitempty"`
Branch string `json:"branch,omitempty"`
RepoCloneURL string `json:"repo_clone_url,omitempty"`
Branch string `json:"branch,omitempty"`
}
// VariableScope the model 'VariableScope'
type VariableScope string
// DeploymentProgressStep struct for DeploymentProgressStep
type DeploymentProgressStep struct {
Name string `json:"name,omitempty"`
Status DeploymentProgressStepStatus `json:"status,omitempty"`
Steps []*DeploymentProgressStep `json:"steps,omitempty"`
StartedAt time.Time `json:"started_at,omitempty"`
EndedAt time.Time `json:"ended_at,omitempty"`
Reason *DeploymentProgressStepReason `json:"reason,omitempty"`
ComponentName string `json:"component_name,omitempty"`
// The base of a human-readable description of the step intended to be combined with the component name for presentation. For example: message_base = \"Building service\" component_name = \"api\"
MessageBase string `json:"message_base,omitempty"`
}
// List of VariableScope
// DeploymentProgressStepStatus the model 'DeploymentProgressStepStatus'
type DeploymentProgressStepStatus string
// List of DeploymentProgressStepStatus
const (
VARIABLESCOPE_UNSET VariableScope = "UNSET"
VARIABLESCOPE_RUN_TIME VariableScope = "RUN_TIME"
VARIABLESCOPE_BUILD_TIME VariableScope = "BUILD_TIME"
VARIABLESCOPE_RUN_AND_BUILD_TIME VariableScope = "RUN_AND_BUILD_TIME"
DeploymentProgressStepStatus_Unknown DeploymentProgressStepStatus = "UNKNOWN"
DeploymentProgressStepStatus_Pending DeploymentProgressStepStatus = "PENDING"
DeploymentProgressStepStatus_Running DeploymentProgressStepStatus = "RUNNING"
DeploymentProgressStepStatus_Error DeploymentProgressStepStatus = "ERROR"
DeploymentProgressStepStatus_Success DeploymentProgressStepStatus = "SUCCESS"
)
// VariableType the model 'VariableType'
type VariableType string
// AppRegion struct for AppRegion
type AppRegion struct {
Slug string `json:"slug,omitempty"`
Label string `json:"label,omitempty"`
Flag string `json:"flag,omitempty"`
Continent string `json:"continent,omitempty"`
Disabled bool `json:"disabled,omitempty"`
DataCenters []string `json:"data_centers,omitempty"`
Reason string `json:"reason,omitempty"`
}
// List of VariableType
// DeploymentProgressStepReason struct for DeploymentProgressStepReason
type DeploymentProgressStepReason struct {
Code string `json:"code,omitempty"`
Message string `json:"message,omitempty"`
}
// AppVariableScope the model 'AppVariableScope'
type AppVariableScope string
// List of AppVariableScope
const (
VARIABLETYPE_GENERAL VariableType = "GENERAL"
VARIABLETYPE_SECRET VariableType = "SECRET"
AppVariableScope_Unset AppVariableScope = "UNSET"
AppVariableScope_RunTime AppVariableScope = "RUN_TIME"
AppVariableScope_BuildTime AppVariableScope = "BUILD_TIME"
AppVariableScope_RunAndBuildTime AppVariableScope = "RUN_AND_BUILD_TIME"
)
// AppVariableType the model 'AppVariableType'
type AppVariableType string
// List of AppVariableType
const (
AppVariableType_General AppVariableType = "GENERAL"
AppVariableType_Secret AppVariableType = "SECRET"
)

View File

@ -4,7 +4,6 @@ import (
"context"
"fmt"
"net/http"
"time"
)
const (
@ -39,71 +38,6 @@ type AppsService interface {
GetLogs(ctx context.Context, appID, deploymentID, component string, logType AppLogType, follow bool) (*AppLogs, *Response, error)
}
// App represents an app.
type App struct {
ID string `json:"id"`
Spec *AppSpec `json:"spec"`
DefaultIngress string `json:"default_ingress"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
ActiveDeployment *Deployment `json:"active_deployment,omitempty"`
InProgressDeployment *Deployment `json:"in_progress_deployment,omitempty"`
}
// Deployment represents a deployment for an app.
type Deployment struct {
ID string `json:"id"`
Spec *AppSpec `json:"spec"`
Services []*DeploymentService `json:"services,omitempty"`
Workers []*DeploymentWorker `json:"workers,omitempty"`
StaticSites []*DeploymentStaticSite `json:"static_sites,omitempty"`
Cause string `json:"cause"`
Progress *DeploymentProgress `json:"progress"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
}
// DeploymentService represents a service component in a deployment.
type DeploymentService struct {
Name string `json:"name,omitempty"`
SourceCommitHash string `json:"source_commit_hash"`
}
// DeploymentWorker represents a worker component in a deployment.
type DeploymentWorker struct {
Name string `json:"name,omitempty"`
SourceCommitHash string `json:"source_commit_hash"`
}
// DeploymentStaticSite represents a static site component in a deployment.
type DeploymentStaticSite struct {
Name string `json:"name,omitempty"`
SourceCommitHash string `json:"source_commit_hash"`
}
// DeploymentProgress represents the total progress of a deployment.
type DeploymentProgress struct {
PendingSteps int `json:"pending_steps"`
RunningSteps int `json:"running_steps"`
SuccessSteps int `json:"success_steps"`
ErrorSteps int `json:"error_steps"`
TotalSteps int `json:"total_steps"`
Steps []*DeploymentProgressStep `json:"steps"`
}
// DeploymentProgressStep represents the progress of a deployment step.
type DeploymentProgressStep struct {
Name string `json:"name"`
Status string `json:"status"`
Steps []*DeploymentProgressStep `json:"steps,omitempty"`
Attempts uint32 `json:"attempts"`
StartedAt time.Time `json:"started_at,omitempty"`
EndedAt time.Time `json:"ended_at,omitempty"`
}
// AppLogs represent app logs.
type AppLogs struct {
LiveURL string `json:"live_url"`
@ -141,7 +75,7 @@ type AppsServiceOp struct {
client *Client
}
// Creates an app.
// Create an app.
func (s *AppsServiceOp) Create(ctx context.Context, create *AppCreateRequest) (*App, *Response, error) {
path := appsBasePath
req, err := s.client.NewRequest(ctx, http.MethodPost, path, create)
@ -264,7 +198,11 @@ func (s *AppsServiceOp) CreateDeployment(ctx context.Context, appID string) (*De
// GetLogs retrieves app logs.
func (s *AppsServiceOp) GetLogs(ctx context.Context, appID, deploymentID, component string, logType AppLogType, follow bool) (*AppLogs, *Response, error) {
url := fmt.Sprintf("%s/%s/deployments/%s/components/%s/logs?type=%s&follow=%t", appsBasePath, appID, deploymentID, component, logType, follow)
url := fmt.Sprintf("%s/%s/deployments/%s/logs?type=%s&follow=%t", appsBasePath, appID, deploymentID, logType, follow)
if component != "" {
url = fmt.Sprintf("%s&component_name=%s", url, component)
}
req, err := s.client.NewRequest(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, nil, err

View File

@ -19,7 +19,7 @@ import (
)
const (
libraryVersion = "1.42.1"
libraryVersion = "1.45.0"
defaultBaseURL = "https://api.digitalocean.com/"
userAgent = "godo/" + libraryVersion
mediaType = "application/json"

View File

@ -83,6 +83,21 @@ type KubernetesClusterUpgradeRequest struct {
VersionSlug string `json:"version,omitempty"`
}
// Taint represents a Kubernetes taint that can be associated with a node pool
// (and, transitively, with all nodes of that pool).
type Taint struct {
Key string
Value string
Effect string
}
func (t Taint) String() string {
if t.Value == "" {
return fmt.Sprintf("%s:%s", t.Key, t.Effect)
}
return fmt.Sprintf("%s=%s:%s", t.Key, t.Value, t.Effect)
}
// KubernetesNodePoolCreateRequest represents a request to create a node pool for a
// Kubernetes cluster.
type KubernetesNodePoolCreateRequest struct {
@ -91,6 +106,7 @@ type KubernetesNodePoolCreateRequest struct {
Count int `json:"count,omitempty"`
Tags []string `json:"tags,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
Taints []Taint `json:"taints,omitempty"`
AutoScale bool `json:"auto_scale,omitempty"`
MinNodes int `json:"min_nodes,omitempty"`
MaxNodes int `json:"max_nodes,omitempty"`
@ -103,6 +119,7 @@ type KubernetesNodePoolUpdateRequest struct {
Count *int `json:"count,omitempty"`
Tags []string `json:"tags,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
Taints *[]Taint `json:"taints,omitempty"`
AutoScale *bool `json:"auto_scale,omitempty"`
MinNodes *int `json:"min_nodes,omitempty"`
MaxNodes *int `json:"max_nodes,omitempty"`
@ -308,6 +325,7 @@ type KubernetesNodePool struct {
Count int `json:"count,omitempty"`
Tags []string `json:"tags,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
Taints []Taint `json:"taints,omitempty"`
AutoScale bool `json:"auto_scale,omitempty"`
MinNodes int `json:"min_nodes,omitempty"`
MaxNodes int `json:"max_nodes,omitempty"`

2
vendor/modules.txt vendored
View File

@ -80,7 +80,7 @@ github.com/census-instrumentation/opencensus-proto/gen-go/trace/v1
github.com/davecgh/go-spew/spew
# github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/dgrijalva/jwt-go
# github.com/digitalocean/godo v1.42.1
# github.com/digitalocean/godo v1.45.0
github.com/digitalocean/godo
# github.com/fatih/color v1.7.0
github.com/fatih/color

View File

@ -13,6 +13,9 @@
<li<%= sidebar_current("docs-do-datasource") %>>
<a href="#">Data Sources</a>
<ul class="nav nav-visible">
<li<%= sidebar_current("docs-do-datasource-app") %>>
<a href="/docs/providers/do/d/app.html">digitalocean_app</a>
</li>
<li<%= sidebar_current("docs-do-datasource-account") %>>
<a href="/docs/providers/do/d/account.html">digitalocean_account</a>
</li>
@ -109,6 +112,9 @@
<li<%= sidebar_current("docs-do-resource") %>>
<a href="#">Resources</a>
<ul class="nav nav-visible">
<li<%= sidebar_current("docs-do-resource-app") %>>
<a href="/docs/providers/do/r/app.html">digitalocean_app</a>
</li>
<li<%= sidebar_current("docs-do-resource-cdn") %>>
<a href="/docs/providers/do/r/cdn.html">digitalocean_cdn</a>
</li>

123
website/docs/d/app.html.md Normal file
View File

@ -0,0 +1,123 @@
---
layout: "digitalocean"
page_title: "DigitalOcean: digitalocean_app"
sidebar_current: "docs-do-datasource-app"
description: |-
Get information on a DigitalOcean App.
---
# digitalocean_app
Get information on a DigitalOcean App.
## Example Usage
Get the account:
```hcl
data "digitalocean_app" "example" {
app_id = "e665d18d-7b56-44a9-92ce-31979174d544"
}
output "default_ingress" {
value = data.digitalocean_app.example.default_ingress
}
```
## Argument Reference
* `app_id` - The ID of the app to retrieve information about.
## Attributes Reference
The following attributes are exported:
* `default_ingress` - The default URL to access the app.
* `live_url` - The live URL of the app.
* `active_deployment_id` - The ID the app's currently active deployment.
* `updated_at` - The date and time of when the app was last updated.
* `created_at` - The date and time of when the app was created.
* `spec` - A DigitalOcean App spec describing the app.
A spec can contain multiple components.
A `service` can contain:
* `name` - The name of the component
* `build_command` - An optional build command to run while building this component from source.
* `dockerfile_path` - The path to a Dockerfile relative to the root of the repo. If set, overrides usage of buildpacks.
* `source_dir` - An optional path to the working directory to use for the build.
* `run_command` - An optional run command to override the component's default.
* `environment_slug` - An environment slug describing the type of this app.
* `instance_size_slug` - The instance size to use for this component.
* `instance_count` - The amount of instances that this component should be scaled to.
* `http_port` - The internal port on which this service's run command will listen.
* `git` - A Git repo to use as component's source. Only one of `git` and `github` may be set.
- `repo_clone_url` - The clone URL of the repo.
- `branch` - The name of the branch to use.
* `github` - A GitHub repo to use as component's source. Only one of `git` and `github` may be set.
- `repo` - The name of the repo in the format `owner/repo`.
- `branch` - The name of the branch to use.
- `deploy_on_push` - Whether to automatically deploy new commits made to the repo.
* `env` - Describes an environment variable made available to an app competent.
- `key` - The name of the environment variable.
- `value` - The value of the environment variable.
- `scope` - The visibility scope of the environment variable. One of `RUN_TIME`, `BUILD_TIME`, or `RUN_AND_BUILD_TIME` (default).
- `type` - The type of the environment variable, `GENERAL` or `SECRET`.
* `route` - An HTTP paths that should be routed to this component.
- `path` - Paths must start with `/` and must be unique within the app.
* `health_check` - A health check to determine the availability of this component.
- `http_path` - The route path used for the HTTP health check ping.
- `initial_delay_seconds` - The number of seconds to wait before beginning health checks.
- `period_seconds` - The number of seconds to wait between health checks.
- `timeout_seconds` - The number of seconds after which the check times out.
- `success_threshold` - The number of successful health checks before considered healthy.
- `failure_threshold` - The number of failed health checks before considered unhealthy.
A `worker` can contain:
* `name` - The name of the component
* `build_command` - An optional build command to run while building this component from source.
* `dockerfile_path` - The path to a Dockerfile relative to the root of the repo. If set, overrides usage of buildpacks.
* `source_dir` - An optional path to the working directory to use for the build.
* `run_command` - An optional run command to override the component's default.
* `environment_slug` - An environment slug describing the type of this app.
* `instance_size_slug` - The instance size to use for this component.
* `instance_count` - The amount of instances that this component should be scaled to.
* `git` - A Git repo to use as component's source. Only one of `git` and `github` may be set.
- `repo_clone_url` - The clone URL of the repo.
- `branch` - The name of the branch to use.
* `github` - A GitHub repo to use as component's source. Only one of `git` and `github` may be set.
- `repo` - The name of the repo in the format `owner/repo`.
- `branch` - The name of the branch to use.
- `deploy_on_push` - Whether to automatically deploy new commits made to the repo.
* `env` - Describes an environment variable made available to an app competent.
- `key` - The name of the environment variable.
- `value` - The value of the environment variable.
- `scope` - The visibility scope of the environment variable. One of `RUN_TIME`, `BUILD_TIME`, or `RUN_AND_BUILD_TIME` (default).
- `type` - The type of the environment variable, `GENERAL` or `SECRET`.
A `static_site` can contain:
* `name` - The name of the component
* `build_command` - An optional build command to run while building this component from source.
* `dockerfile_path` - The path to a Dockerfile relative to the root of the repo. If set, overrides usage of buildpacks.
* `source_dir` - An optional path to the working directory to use for the build.
* `environment_slug` - An environment slug describing the type of this app.
* `output_dir` - An optional path to where the built assets will be located, relative to the build context. If not set, App Platform will automatically scan for these directory names: `_static`, `dist`, `public`.
* `index_document` - The name of the index document to use when serving this static site.
* `error_document` - The name of the error document to use when serving this static site*
* `git` - A Git repo to use as component's source. Only one of `git` and `github` may be set.
- `repo_clone_url` - The clone URL of the repo.
- `branch` - The name of the branch to use.
* `github` - A GitHub repo to use as component's source. Only one of `git` and `github` may be set.
- `repo` - The name of the repo in the format `owner/repo`.
- `branch` - The name of the branch to use.
- `deploy_on_push` - Whether to automatically deploy new commits made to the repo.
* `env` - Describes an environment variable made available to an app competent.
- `key` - The name of the environment variable.
- `value` - The value of the environment variable.
- `scope` - The visibility scope of the environment variable. One of `RUN_TIME`, `BUILD_TIME`, or `RUN_AND_BUILD_TIME` (default).
- `type` - The type of the environment variable, `GENERAL` or `SECRET`.
* `route` - An HTTP paths that should be routed to this component.
- `path` - Paths must start with `/` and must be unique within the app.

View File

@ -0,0 +1,223 @@
---
layout: "digitalocean"
page_title: "DigitalOcean: digitalocean_app"
sidebar_current: "docs-do-resource-app"
description: |-
Provides a DigitalOcean App resource.
---
# digitalocean\_app
Provides a DigitalOcean App resource.
## Example Usage
To create an app, provide a [DigitalOcean app spec](https://www.digitalocean.com/docs/app-platform/resources/app-specification-reference/) specifying the app's components.
### Basic Example
```hcl
resource "digitalocean_app" "golang-sample" {
spec {
name = "golang-sample"
region = "ams"
service {
name = "go-service"
environment_slug = "go"
instance_count = 1
instance_size_slug = "professional-xs"
git {
repo_clone_url = "https://github.com/digitalocean/sample-golang.git"
branch = "main"
}
}
}
}
```
### Static Site Example
```hcl
resource "digitalocean_app" "static-ste-example" {
spec {
name = "static-ste-example"
region = "ams"
static_site {
name = "sample-jekyll"
build_command = "bundle exec jekyll build -d ./public"
output_dir = "/public"
git {
repo_clone_url = "https://github.com/digitalocean/sample-jekyll.git"
branch = "main"
}
}
}
}
```
### Multiple Components Example
```hcl
resource "digitalocean_app" "mono-repo-example" {
spec {
name = "mono-repo-example"
region = "ams"
domains = ["foo.example.com"]
# Build a Go project in the api/ directory that listens on port 3000
# and serves it at https://foo.example.com/api
service {
name = "api"
environment_slug = "go"
instance_count = 2
instance_size_slug = "professional-xs"
github {
branch = "main"
deploy_on_push = true
repo = "username/repo"
}
source_dir = "api/"
http_port = 3000
routes {
path = "/api"
}
run_command = "bin/api"
}
# Builds a static site in the project's root directory
# and serves it at https://foo.example.com/
static_site {
name = "web"
build_command = "npm run build"
github {
branch = "main"
deploy_on_push = true
repo = "username/repo"
}
routes {
path = "/"
}
}
}
}
```
## Argument Reference
The following arguments are supported:
* `spec` - (Required) A DigitalOcean App spec describing the app.
- `name` - (Required) The name of the app. Must be unique across all apps in the same account.
- `region` - The slug for the DigitalOcean data center region hosting the app.
- `domains` - A list of hostnames where the application will be available.
A spec can contain multiple components.
A `service` can contain:
* `name` - The name of the component
* `build_command` - An optional build command to run while building this component from source.
* `dockerfile_path` - The path to a Dockerfile relative to the root of the repo. If set, overrides usage of buildpacks.
* `source_dir` - An optional path to the working directory to use for the build.
* `run_command` - An optional run command to override the component's default.
* `environment_slug` - An environment slug describing the type of this app.
* `instance_size_slug` - The instance size to use for this component.
* `instance_count` - The amount of instances that this component should be scaled to.
* `http_port` - The internal port on which this service's run command will listen.
* `git` - A Git repo to use as component's source. Only one of `git` and `github` may be set.
- `repo_clone_url` - The clone URL of the repo.
- `branch` - The name of the branch to use.
* `github` - A GitHub repo to use as component's source. Only one of `git` and `github` may be set.
- `repo` - The name of the repo in the format `owner/repo`.
- `branch` - The name of the branch to use.
- `deploy_on_push` - Whether to automatically deploy new commits made to the repo.
* `env` - Describes an environment variable made available to an app competent.
- `key` - The name of the environment variable.
- `value` - The value of the environment variable.
- `scope` - The visibility scope of the environment variable. One of `RUN_TIME`, `BUILD_TIME`, or `RUN_AND_BUILD_TIME` (default).
- `type` - The type of the environment variable, `GENERAL` or `SECRET`.
* `route` - An HTTP paths that should be routed to this component.
- `path` - Paths must start with `/` and must be unique within the app.
* `health_check` - A health check to determine the availability of this component.
- `http_path` - The route path used for the HTTP health check ping.
- `initial_delay_seconds` - The number of seconds to wait before beginning health checks.
- `period_seconds` - The number of seconds to wait between health checks.
- `timeout_seconds` - The number of seconds after which the check times out.
- `success_threshold` - The number of successful health checks before considered healthy.
- `failure_threshold` - The number of failed health checks before considered unhealthy.
A `worker` can contain:
* `name` - The name of the component
* `build_command` - An optional build command to run while building this component from source.
* `dockerfile_path` - The path to a Dockerfile relative to the root of the repo. If set, overrides usage of buildpacks.
* `source_dir` - An optional path to the working directory to use for the build.
* `run_command` - An optional run command to override the component's default.
* `environment_slug` - An environment slug describing the type of this app.
* `instance_size_slug` - The instance size to use for this component.
* `instance_count` - The amount of instances that this component should be scaled to.
* `git` - A Git repo to use as component's source. Only one of `git` and `github` may be set.
- `repo_clone_url` - The clone URL of the repo.
- `branch` - The name of the branch to use.
* `github` - A GitHub repo to use as component's source. Only one of `git` and `github` may be set.
- `repo` - The name of the repo in the format `owner/repo`.
- `branch` - The name of the branch to use.
- `deploy_on_push` - Whether to automatically deploy new commits made to the repo.
* `env` - Describes an environment variable made available to an app competent.
- `key` - The name of the environment variable.
- `value` - The value of the environment variable.
- `scope` - The visibility scope of the environment variable. One of `RUN_TIME`, `BUILD_TIME`, or `RUN_AND_BUILD_TIME` (default).
- `type` - The type of the environment variable, `GENERAL` or `SECRET`.
A `static_site` can contain:
* `name` - The name of the component
* `build_command` - An optional build command to run while building this component from source.
* `dockerfile_path` - The path to a Dockerfile relative to the root of the repo. If set, overrides usage of buildpacks.
* `source_dir` - An optional path to the working directory to use for the build.
* `environment_slug` - An environment slug describing the type of this app.
* `output_dir` - An optional path to where the built assets will be located, relative to the build context. If not set, App Platform will automatically scan for these directory names: `_static`, `dist`, `public`.
* `index_document` - The name of the index document to use when serving this static site.
* `error_document` - The name of the error document to use when serving this static site*
* `git` - A Git repo to use as component's source. Only one of `git` and `github` may be set.
- `repo_clone_url` - The clone URL of the repo.
- `branch` - The name of the branch to use.
* `github` - A GitHub repo to use as component's source. Only one of `git` and `github` may be set.
- `repo` - The name of the repo in the format `owner/repo`.
- `branch` - The name of the branch to use.
- `deploy_on_push` - Whether to automatically deploy new commits made to the repo.
* `env` - Describes an environment variable made available to an app competent.
- `key` - The name of the environment variable.
- `value` - The value of the environment variable.
- `scope` - The visibility scope of the environment variable. One of `RUN_TIME`, `BUILD_TIME`, or `RUN_AND_BUILD_TIME` (default).
- `type` - The type of the environment variable, `GENERAL` or `SECRET`.
* `route` - An HTTP paths that should be routed to this component.
- `path` - Paths must start with `/` and must be unique within the app.
## Attributes Reference
In addition to the above attributes, the following are exported:
* `default_ingress` - The default URL to access the app.
* `live_url` - The live URL of the app.
* `active_deployment_id` - The ID the app's currently active deployment.
* `updated_at` - The date and time of when the app was last updated.
* `created_at` - The date and time of when the app was created.
## Import
An app can be imported using its `id`, e.g.
```
terraform import digitalocean_app.myapp fb06ad00-351f-45c8-b5eb-13523c438661
```