Skip to content

Commit

Permalink
feat: docker
Browse files Browse the repository at this point in the history
  • Loading branch information
miton18 committed Jul 3, 2024
1 parent 093eb22 commit 78022d1
Show file tree
Hide file tree
Showing 11 changed files with 562 additions and 19 deletions.
76 changes: 76 additions & 0 deletions docs/resources/docker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "clevercloud_docker Resource - terraform-provider-clevercloud"
subcategory: ""
description: |-
Manage Docker https:// applications.
See Docker product https://www.clever-cloud.com/doc/getting-started/by-language/docker/ specification.
---

# clevercloud_docker (Resource)

Manage [Docker](https://) applications.

See [Docker product](https://www.clever-cloud.com/doc/getting-started/by-language/docker/) specification.



<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `biggest_flavor` (String) Biggest intance flavor, if different from smallest, enable autoscaling
- `max_instance_count` (Number) Maximum instance count, if different from min value, enable autoscaling
- `min_instance_count` (Number) Minimum instance count
- `name` (String) Application name
- `smallest_flavor` (String) Smallest instance flavor

### Optional

- `additional_vhosts` (List of String) Add custom hostname in addition to the default one, see [documentation](https://www.clever-cloud.com/doc/administrate/domain-names/)
- `app_folder` (String) Folder in which the application is located (inside the git repository)
- `build_flavor` (String) Use dedicated instance with given flavor for build step
- `container_port` (Number) Set to custom HTTP port if your Docker container runs on custom port
- `container_port_tcp` (Number) Set to custom TCP port if your Docker container runs on custom port.
- `daemon_socket_mount` (Boolean) Set to true to access the host Docker socket from inside your container
- `dependencies` (Set of String) A list of application or addons requires to run this application.
Can be either app_xxx or postgres_yyy ID format
- `deployment` (Block, Optional) (see [below for nested schema](#nestedblock--deployment))
- `description` (String) Application description
- `dockerfile` (String) The name of the Dockerfile to build
- `enable_ipv6` (Boolean) Activate the support of IPv6 with an IPv6 subnet int the docker daemon
- `environment` (Map of String, Sensitive) Environment variables injected into the application
- `hooks` (Block, Optional) (see [below for nested schema](#nestedblock--hooks))
- `redirect_https` (Boolean) Redirect client from plain to TLS port
- `region` (String) Geographical region where the database will be deployed
- `registry_password` (String) The password of your username
- `registry_url` (String) The server of your private registry (optional). Docker’s public registry
- `registry_user` (String) The username to login to a private registry
- `sticky_sessions` (Boolean) Enable sticky sessions, use it when your client sessions are instances scoped

### Read-Only

- `deploy_url` (String) Git URL used to push source code
- `id` (String) Unique identifier generated during application creation
- `vhost` (String) Default vhost to access your app

<a id="nestedblock--deployment"></a>
### Nested Schema for `deployment`

Optional:

- `commit` (String) Deploy application on the given commit/tag
- `repository` (String)


<a id="nestedblock--hooks"></a>
### Nested Schema for `hooks`

Optional:

- `post_build` (String) [CC_POST_BUILD_HOOK](https://www.clever-cloud.com/doc/develop/build-hooks/#post-build-cc_post_build_hook)
- `pre_build` (String) [CC_PRE_BUILD_HOOK](https://www.clever-cloud.com/doc/develop/build-hooks/#pre-build-cc_pre_build_hook)
- `pre_run` (String) [CC_PRE_RUN_HOOK](https://www.clever-cloud.com/doc/develop/build-hooks/#pre-run-cc_pre_run_hook)
- `run_failed` (String) [CC_RUN_FAILED_HOOK](https://www.clever-cloud.com/doc/develop/build-hooks/#run-succeeded-cc_run_succeeded_hook-or-failed-cc_run_failed_hook)
- `run_succeed` (String) [CC_RUN_SUCCEEDED_HOOK](https://www.clever-cloud.com/doc/develop/build-hooks/#run-succeeded-cc_run_succeeded_hook-or-failed-cc_run_failed_hook)
2 changes: 2 additions & 0 deletions pkg/registry/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"go.clever-cloud.com/terraform-provider/pkg/resources/addon"
"go.clever-cloud.com/terraform-provider/pkg/resources/cellar"
"go.clever-cloud.com/terraform-provider/pkg/resources/cellar/bucket"
"go.clever-cloud.com/terraform-provider/pkg/resources/docker"
"go.clever-cloud.com/terraform-provider/pkg/resources/java"
"go.clever-cloud.com/terraform-provider/pkg/resources/materiakv"
"go.clever-cloud.com/terraform-provider/pkg/resources/mongodb"
Expand All @@ -32,4 +33,5 @@ var Resources = []func() resource.Resource{
python.NewResourcePython,
scala.NewResourceScala(),
static.NewResourceStatic(),
docker.NewResourceDocker,
}
21 changes: 21 additions & 0 deletions pkg/resources/docker/resource_docker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package docker

import (
"context"

"github.com/hashicorp/terraform-plugin-framework/resource"
"go.clever-cloud.dev/client"
)

type ResourceDocker struct {
cc *client.Client
org string
}

func NewResourceDocker() resource.Resource {
return &ResourceDocker{}
}

func (r *ResourceDocker) Metadata(ctx context.Context, req resource.MetadataRequest, res *resource.MetadataResponse) {
res.TypeName = req.ProviderTypeName + "_docker"
}
3 changes: 3 additions & 0 deletions pkg/resources/docker/resource_docker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Manage [Docker](https://) applications.

See [Docker product](https://www.clever-cloud.com/doc/getting-started/by-language/docker/) specification.
198 changes: 198 additions & 0 deletions pkg/resources/docker/resource_docker_crud.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
package docker

import (
"context"

"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
"go.clever-cloud.com/terraform-provider/pkg"
"go.clever-cloud.com/terraform-provider/pkg/application"
"go.clever-cloud.com/terraform-provider/pkg/provider"
"go.clever-cloud.com/terraform-provider/pkg/tmp"
)

// Weird behaviour, but TF can ask for a Resource without having configured a Provider (maybe for Meta and Schema)
// So we need to handle the case there is no ProviderData
func (r *ResourceDocker) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
tflog.Debug(ctx, "ResourceDocker.Configure()")

// Prevent panic if the provider has not been configured.
if req.ProviderData == nil {
return
}

provider, ok := req.ProviderData.(provider.Provider)
if ok {
r.cc = provider.Client()
r.org = provider.Organization()
}

tflog.Debug(ctx, "AFTER CONFIGURED", map[string]interface{}{"cc": r.cc == nil, "org": r.org})
}

// Create a new resource
func (r *ResourceDocker) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
plan := Docker{}

resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
if resp.Diagnostics.HasError() {
return
}

instance := application.LookupInstance(ctx, r.cc, "docker", "Docker", resp.Diagnostics)
if resp.Diagnostics.HasError() {
return
}

vhosts := []string{}
resp.Diagnostics.Append(plan.AdditionalVHosts.ElementsAs(ctx, &vhosts, false)...)
if resp.Diagnostics.HasError() {
return
}

environment := plan.toEnv(ctx, resp.Diagnostics)
if resp.Diagnostics.HasError() {
return
}

createAppReq := application.CreateReq{
Client: r.cc,
Organization: r.org,
Application: tmp.CreateAppRequest{
Name: plan.Name.ValueString(),
Deploy: "git",
Description: plan.Description.ValueString(),
InstanceType: instance.Type,
InstanceVariant: instance.Variant.ID,
InstanceVersion: instance.Version,
BuildFlavor: plan.BuildFlavor.ValueString(),
MinFlavor: plan.SmallestFlavor.ValueString(),
MaxFlavor: plan.BiggestFlavor.ValueString(),
MinInstances: plan.MinInstanceCount.ValueInt64(),
MaxInstances: plan.MaxInstanceCount.ValueInt64(),
StickySessions: plan.StickySessions.ValueBool(),
ForceHttps: application.FromForceHTTPS(plan.RedirectHTTPS.ValueBool()),
Zone: plan.Region.ValueString(),
CancelOnPush: false,
},
Environment: environment,
VHosts: vhosts,
Deployment: plan.toDeployment(),
}

createAppRes, diags := application.CreateApp(ctx, createAppReq)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}

tflog.Debug(ctx, "BUILD FLAVOR RES"+createAppRes.Application.BuildFlavor.Name, map[string]interface{}{})
plan.ID = pkg.FromStr(createAppRes.Application.ID)
plan.DeployURL = pkg.FromStr(createAppRes.Application.DeployURL)
plan.VHost = pkg.FromStr(createAppRes.Application.Vhosts[0].Fqdn)

resp.Diagnostics.Append(resp.State.Set(ctx, plan)...)
if resp.Diagnostics.HasError() {
return
}
}

// Read resource information
func (r *ResourceDocker) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var state Docker

resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}

appPHP, diags := application.ReadApp(ctx, r.cc, r.org, state.ID.ValueString())
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
if appPHP.AppIsDeleted {
resp.State.RemoveResource(ctx)
return
}

state.Name = pkg.FromStr(appPHP.App.Name)
state.Description = pkg.FromStr(appPHP.App.Description)
state.MinInstanceCount = pkg.FromI(int64(appPHP.App.Instance.MinInstances))
state.MaxInstanceCount = pkg.FromI(int64(appPHP.App.Instance.MaxInstances))
state.SmallestFlavor = pkg.FromStr(appPHP.App.Instance.MinFlavor.Name)
state.BiggestFlavor = pkg.FromStr(appPHP.App.Instance.MaxFlavor.Name)
state.Region = pkg.FromStr(appPHP.App.Zone)
state.DeployURL = pkg.FromStr(appPHP.App.DeployURL)

if appPHP.App.SeparateBuild {
state.BuildFlavor = pkg.FromStr(appPHP.App.BuildFlavor.Name)
} else {
state.BuildFlavor = types.StringNull()
}

vhosts := pkg.Map(appPHP.App.Vhosts, func(vhost tmp.Vhost) string {
return vhost.Fqdn
})
hasDefaultVHost := pkg.HasSome(vhosts, func(vhost string) bool {
return pkg.VhostCleverAppsRegExp.MatchString(vhost)
})
if hasDefaultVHost {
cleverapps := *pkg.First(vhosts, func(vhost string) bool {
return pkg.VhostCleverAppsRegExp.MatchString(vhost)
})
state.VHost = pkg.FromStr(cleverapps)
} else {
state.VHost = types.StringNull()
}

vhostsWithoutDefault := pkg.Filter(vhosts, func(vhost string) bool {
ok := pkg.VhostCleverAppsRegExp.MatchString(vhost)
return !ok
})
if len(vhostsWithoutDefault) > 0 {
state.AdditionalVHosts = pkg.FromListString(vhostsWithoutDefault)
} else {
state.AdditionalVHosts = types.ListNull(types.StringType)
}

resp.Diagnostics.Append(resp.State.Set(ctx, state)...)
}

// Update resource
func (r *ResourceDocker) Update(ctx context.Context, req resource.UpdateRequest, res *resource.UpdateResponse) {
// TODO
}

// Delete resource
func (r *ResourceDocker) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var state Docker

resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
tflog.Debug(ctx, "DOCKER DELETE", map[string]interface{}{"state": state})

res := tmp.DeleteApp(ctx, r.cc, r.org, state.ID.ValueString())
if res.IsNotFoundError() {
resp.State.RemoveResource(ctx)
return
}
if res.HasError() {
resp.Diagnostics.AddError("failed to delete app", res.Error().Error())
return
}

resp.State.RemoveResource(ctx)
}

// Import resource
func (r *ResourceDocker) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
// Save the import identifier in the id attribute
// and call Read() to fill fields
attr := path.Root("id")
resource.ImportStatePassthroughID(ctx, attr, req, resp)
}
Loading

0 comments on commit 78022d1

Please sign in to comment.