Skip to content
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

Add support for Job and CronJob (closes #86 and #212) #411

Merged
merged 20 commits into from
Jun 24, 2019
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
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ require (
github.com/mitchellh/go-homedir v1.1.0
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pierrec/lz4 v2.0.5+incompatible // indirect
github.com/robfig/cron v1.2.0
github.com/stoewer/go-strcase v1.0.2 // indirect
github.com/terraform-providers/terraform-provider-aws v0.0.0-20190510001811-4b894dbf13f6
github.com/terraform-providers/terraform-provider-google v1.20.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,8 @@ github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=
github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
Expand Down
2 changes: 2 additions & 0 deletions kubernetes/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,11 +141,13 @@ func Provider() terraform.ResourceProvider {
"kubernetes_cluster_role": resourceKubernetesClusterRole(),
"kubernetes_cluster_role_binding": resourceKubernetesClusterRoleBinding(),
"kubernetes_config_map": resourceKubernetesConfigMap(),
"kubernetes_cron_job": resourceKubernetesCronJob(),
"kubernetes_daemonset": resourceKubernetesDaemonSet(),
"kubernetes_deployment": resourceKubernetesDeployment(),
"kubernetes_endpoints": resourceKubernetesEndpoints(),
"kubernetes_horizontal_pod_autoscaler": resourceKubernetesHorizontalPodAutoscaler(),
"kubernetes_ingress": resourceKubernetesIngress(),
"kubernetes_job": resourceKubernetesJob(),
"kubernetes_limit_range": resourceKubernetesLimitRange(),
"kubernetes_namespace": resourceKubernetesNamespace(),
"kubernetes_network_policy": resourceKubernetesNetworkPolicy(),
Expand Down
209 changes: 209 additions & 0 deletions kubernetes/resource_kubernetes_cron_job.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
package kubernetes

import (
"fmt"
"log"
"time"

"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
"k8s.io/api/batch/v1beta1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kubernetes "k8s.io/client-go/kubernetes"
)

func resourceKubernetesCronJob() *schema.Resource {
return &schema.Resource{
Create: resourceKubernetesCronJobCreate,
Read: resourceKubernetesCronJobRead,
Update: resourceKubernetesCronJobUpdate,
Delete: resourceKubernetesCronJobDelete,
Exists: resourceKubernetesCronJobExists,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Schema: map[string]*schema.Schema{
"metadata": namespacedMetadataSchema("cronjob", true),
"spec": {
Type: schema.TypeList,
Description: "Spec of the cron job owned by the cluster",
Required: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: cronJobSpecFields(),
},
},
},
}
}

func resourceKubernetesCronJobCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*kubernetes.Clientset)

metadata := expandMetadata(d.Get("metadata").([]interface{}))
spec, err := expandCronJobSpec(d.Get("spec").([]interface{}))
if err != nil {
return err
}

job := v1beta1.CronJob{
ObjectMeta: metadata,
Spec: spec,
}

log.Printf("[INFO] Creating new cron job: %#v", job)

out, err := conn.BatchV1beta1().CronJobs(metadata.Namespace).Create(&job)
if err != nil {
return err
Copy link
Member

@alexsomesan alexsomesan Apr 24, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mind wrapping this error with a message mentioning the Create operation failed?

Suggested change
return err
return fmt.Errorf("Failed to create CronJob: %s", err)

}
log.Printf("[INFO] Submitted new cron job: %#v", out)

d.SetId(buildId(out.ObjectMeta))

return resourceKubernetesCronJobRead(d, meta)
}

func resourceKubernetesCronJobUpdate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*kubernetes.Clientset)

namespace, _, err := idParts(d.Id())
if err != nil {
return err
}

metadata := expandMetadata(d.Get("metadata").([]interface{}))
spec, err := expandCronJobSpec(d.Get("spec").([]interface{}))
if err != nil {
return err
}
spec.JobTemplate.ObjectMeta.Annotations = metadata.Annotations

cronjob := &v1beta1.CronJob{
ObjectMeta: metadata,
Spec: spec,
}

log.Printf("[INFO] Updating cron job %s: %s", d.Id(), cronjob)

out, err := conn.BatchV1beta1().CronJobs(namespace).Update(cronjob)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Update() client-go operation is doing a complete replacing of the resource, rather then mutating only the fields present in the Terraform diff.

This is generally not the behaviour Terraform users expect when they see a diff with updates, since this wholesale-replace behaviour is modeled in Terraform via a Delete/Create sequence.

To achieve the expected behaviour of in-place updating specific attributes as per Terraform diff, one has to model this operation using d.HasChange(..) to extract changes for the Terraform diff and build a set of JSON-Patch actions to be passed to the Patch(..) operation of the client-go.

Alternatively, if the resource doesn't support in-place update, then the Update operation on the provider resource should just not be implemented.

I notice the Job resource also introduced in this PR does make proper use of patching, so it'd be great to have them both aligned.

if err != nil {
return err
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error wrapping.

}
log.Printf("[INFO] Submitted updated cron job: %#v", out)

d.SetId(buildId(out.ObjectMeta))
return resourceKubernetesCronJobRead(d, meta)
}

func resourceKubernetesCronJobRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*kubernetes.Clientset)

namespace, name, err := idParts(d.Id())
if err != nil {
return err
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error wrapping.

}

log.Printf("[INFO] Reading cron job %s", name)
job, err := conn.BatchV1beta1().CronJobs(namespace).Get(name, metav1.GetOptions{})
if err != nil {
log.Printf("[DEBUG] Received error: %#v", err)
return err
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error wrapping.

}
log.Printf("[INFO] Received cron job: %#v", job)

// Remove server-generated labels unless using manual selector
if _, ok := d.GetOk("spec.0.manual_selector"); !ok {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This dot-syntax type of attribute referencing in d.Get(..) is on a deprecation path and not guaranteed to be supported in TF 0.12 and beyond. It'd be better to move this operation to its appropriate flatten func.

labels := job.ObjectMeta.Labels

if _, ok := labels["controller-uid"]; ok {
delete(labels, "controller-uid")
}

if _, ok := labels["cron-job-name"]; ok {
delete(labels, "cron-job-name")
}

if job.Spec.JobTemplate.Spec.Selector != nil &&
job.Spec.JobTemplate.Spec.Selector.MatchLabels != nil {
labels = job.Spec.JobTemplate.Spec.Selector.MatchLabels
}

if _, ok := labels["controller-uid"]; ok {
delete(labels, "controller-uid")
}
}

err = d.Set("metadata", flattenMetadata(job.ObjectMeta, d))
if err != nil {
return err
}

jobSpec, err := flattenCronJobSpec(job.Spec, d)
if err != nil {
return err
}

err = d.Set("spec", jobSpec)
if err != nil {
return err
}

return nil
}

func resourceKubernetesCronJobDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*kubernetes.Clientset)

namespace, name, err := idParts(d.Id())
if err != nil {
return err
}

log.Printf("[INFO] Deleting cron job: %#v", name)
err = conn.BatchV1beta1().CronJobs(namespace).Delete(name, nil)
if err != nil {
return err
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error wrapping.

}

err = resource.Retry(1*time.Minute, func() *resource.RetryError {
_, err := conn.BatchV1beta1().CronJobs(namespace).Get(name, metav1.GetOptions{})
if err != nil {
if statusErr, ok := err.(*errors.StatusError); ok && statusErr.ErrStatus.Code == 404 {
return nil
}
return resource.NonRetryableError(err)
}

e := fmt.Errorf("Cron Job %s still exists", name)
return resource.RetryableError(e)
})
if err != nil {
return err
}

log.Printf("[INFO] Cron Job %s deleted", name)

d.SetId("")
return nil
}

func resourceKubernetesCronJobExists(d *schema.ResourceData, meta interface{}) (bool, error) {
conn := meta.(*kubernetes.Clientset)

namespace, name, err := idParts(d.Id())
if err != nil {
return false, err
}

log.Printf("[INFO] Checking cron job %s", name)
_, err = conn.BatchV1beta1().CronJobs(namespace).Get(name, metav1.GetOptions{})
if err != nil {
if statusErr, ok := err.(*errors.StatusError); ok && statusErr.ErrStatus.Code == 404 {
return false, nil
}
log.Printf("[DEBUG] Received error: %#v", err)
}
return true, err
}
Loading