Skip to content

Add wait functionality for deployments and daemonsets #215

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jan 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
if: startsWith(matrix.runner, 'ubuntu-')
uses: engineerd/[email protected]
with:
version: "v0.9.0"
version: "v0.17.0"

- name: Set up Go
uses: actions/setup-go@v2
Expand Down
9 changes: 8 additions & 1 deletion docs/resources/resource.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ resource "kustomization_resource" "p0" {

# then loop through resources in ids_prio[1]
# and set an explicit depends_on on kustomization_resource.p0
# wait 2 minutes for any deployment or daemonset to become ready
resource "kustomization_resource" "p1" {
for_each = data.kustomization_build.test.ids_prio[1]

Expand All @@ -76,6 +77,11 @@ resource "kustomization_resource" "p1" {
? sensitive(data.kustomization_build.test.manifests[each.value])
: data.kustomization_build.test.manifests[each.value]
)
wait = true
timeouts {
create = "2m"
update = "2m"
}

depends_on = [kustomization_resource.p0]
}
Expand All @@ -99,4 +105,5 @@ resource "kustomization_resource" "p2" {
## Argument Reference

- `manifest` - (Required) JSON encoded Kubernetes resource manifest.
- 'timeouts' - (Optional) Overwrite `create` or `delete` timeout defaults. Defaults are 5 minutes for `create` and 10 minutes for `delete`.
- `wait` - Whether to wait for pods to become ready (default false). Currently only has an effect for Deployments and DaemonSets.
- 'timeouts' - (Optional) Overwrite `create`, `update` or `delete` timeout defaults. Defaults are 5 minutes for `create` and `update` and 10 minutes for `delete`.
98 changes: 98 additions & 0 deletions kustomize/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ import (

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"

k8sappsv1 "k8s.io/api/apps/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
k8smeta "k8s.io/apimachinery/pkg/api/meta"
k8smetav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

k8sunstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
k8sruntime "k8s.io/apimachinery/pkg/runtime"
k8sschema "k8s.io/apimachinery/pkg/runtime/schema"
Expand All @@ -20,13 +22,20 @@ import (
"k8s.io/client-go/restmapper"
)

var waitRefreshFunctions = map[string]waitRefreshFunction{
"apps/Deployment": waitDeploymentRefresh,
"apps/Daemonset": waitDaemonsetRefresh,
}

type kManifestId struct {
group string
kind string
namespace string
name string
}

type waitRefreshFunction func(km *kManifest) (interface{}, string, error)

func mustParseProviderId(str string) *kManifestId {
kr, err := parseProviderId(str)
if err != nil {
Expand Down Expand Up @@ -354,6 +363,95 @@ func (km *kManifest) waitDeleted(t time.Duration) error {
return nil
}

func daemonsetReady(u *k8sunstructured.Unstructured) (bool, error) {
var daemonset k8sappsv1.DaemonSet
if err := k8sruntime.DefaultUnstructuredConverter.FromUnstructured(u.UnstructuredContent(), &daemonset); err != nil {
return false, err
}
if daemonset.Generation == daemonset.Status.ObservedGeneration &&
daemonset.Status.UpdatedNumberScheduled == daemonset.Status.DesiredNumberScheduled &&
daemonset.Status.NumberReady == daemonset.Status.DesiredNumberScheduled &&
daemonset.Status.NumberUnavailable == 0 {
return true, nil
} else {
return false, nil
}
}

func waitDaemonsetRefresh(km *kManifest) (interface{}, string, error) {
resp, err := km.apiGet(k8smetav1.GetOptions{})
if err != nil {
if k8serrors.IsNotFound(err) {
return nil, "missing", nil
}
return nil, "error", err
}
ready, err := daemonsetReady(resp)
if err != nil {
return nil, "error", err
}
if ready {
return resp, "done", nil
}
return nil, "in progress", nil
}

func deploymentReady(u *k8sunstructured.Unstructured) (bool, error) {
var deployment k8sappsv1.Deployment
if err := k8sruntime.DefaultUnstructuredConverter.FromUnstructured(u.UnstructuredContent(), &deployment); err != nil {
return false, err
}
if deployment.Generation == deployment.Status.ObservedGeneration &&
deployment.Status.AvailableReplicas == *deployment.Spec.Replicas &&
deployment.Status.AvailableReplicas == deployment.Status.Replicas &&
deployment.Status.UnavailableReplicas == 0 {
return true, nil
} else {
return false, nil
}
}

func waitDeploymentRefresh(km *kManifest) (interface{}, string, error) {
resp, err := km.apiGet(k8smetav1.GetOptions{})
if err != nil {
if k8serrors.IsNotFound(err) {
return nil, "missing", nil
}
return nil, "error", err
}
ready, err := deploymentReady(resp)
if err != nil {
return nil, "error", err
}
if ready {
return resp, "done", nil
}
return nil, "in progress", nil
}

func (km *kManifest) waitCreatedOrUpdated(t time.Duration) error {
gvk := km.gvk()
if refresh, ok := waitRefreshFunctions[fmt.Sprintf("%s/%s", gvk.Group, gvk.Kind)]; ok {
delay := 10 * time.Second
stateConf := &resource.StateChangeConf{
Target: []string{"done"},
Pending: []string{"in progress"},
Timeout: t,
Delay: delay,
NotFoundChecks: 2*int(t/delay) + 1,
Refresh: func() (interface{}, string, error) {
return refresh(km)
},
}

_, err := stateConf.WaitForState()
if err != nil {
return km.fmtErr(fmt.Errorf("timed out creating/updating %s %s/%s: %s", gvk.Kind, km.namespace(), km.name(), err))
}
}
return nil
}

func (km *kManifest) fmtErr(err error) error {
return fmt.Errorf(
"%q: %s",
Expand Down
19 changes: 19 additions & 0 deletions kustomize/resource_kustomization.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,16 @@ func kustomizationResource() *schema.Resource {
Type: schema.TypeString,
Required: true,
},
"wait": &schema.Schema{
Type: schema.TypeBool,
Default: false,
Optional: true,
},
},

Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(5 * time.Minute),
Update: schema.DefaultTimeout(5 * time.Minute),
Delete: schema.DefaultTimeout(10 * time.Minute),
},
}
Expand Down Expand Up @@ -103,6 +109,12 @@ func kustomizationResourceCreate(d *schema.ResourceData, m interface{}) error {
return logError(err)
}

if d.Get("wait").(bool) {
if err = km.waitCreatedOrUpdated(d.Timeout(schema.TimeoutCreate)); err != nil {
return logError(err)
}
}

id := string(resp.GetUID())
d.SetId(id)

Expand Down Expand Up @@ -327,6 +339,12 @@ func kustomizationResourceUpdate(d *schema.ResourceData, m interface{}) error {
return logError(err)
}

if d.Get("wait").(bool) {
if err = kmm.waitCreatedOrUpdated(d.Timeout(schema.TimeoutUpdate)); err != nil {
return logError(err)
}
}

id := string(resp.GetUID())
d.SetId(id)

Expand Down Expand Up @@ -421,6 +439,7 @@ func kustomizationResourceImport(d *schema.ResourceData, m interface{}) ([]*sche
}

d.Set("manifest", lac)
d.Set("wait", d.Get("wait"))

return []*schema.ResourceData{d}, nil
}
Loading