Skip to content

Commit

Permalink
Merge pull request hashicorp#12 from sl1pm4t/cron-job-rebase
Browse files Browse the repository at this point in the history
CronJob resource
  • Loading branch information
sl1pm4t authored May 2, 2018
2 parents 95b07a9 + 9bf4d0b commit 1d80a8f
Show file tree
Hide file tree
Showing 14 changed files with 772 additions and 36 deletions.
6 changes: 6 additions & 0 deletions kubernetes/api_versions.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ const (
appsV1
appsV1beta1
appsV1beta2
batchV1beta1
batchV2alpha1
extensionsV1beta1
)

Expand All @@ -32,6 +34,10 @@ func (g APIGroup) String() string {
return "apps/v1beta2"
case extensionsV1beta1:
return "extensions/v1beta1"
case batchV1beta1:
return "batch/v1beta1"
case batchV2alpha1:
return "batch/v2alpha1"
default:
return "none"
}
Expand Down
1 change: 1 addition & 0 deletions kubernetes/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ func Provider() terraform.ResourceProvider {
"kubernetes_config_map": resourceKubernetesConfigMap(),
"kubernetes_horizontal_pod_autoscaler": resourceKubernetesHorizontalPodAutoscaler(),
"kubernetes_job": resourceKubernetesJob(),
"kubernetes_cron_job": resourceKubernetesCronJob(),
"kubernetes_ingress": resourceKubernetesIngress(),
"kubernetes_limit_range": resourceKubernetesLimitRange(),
"kubernetes_namespace": resourceKubernetesNamespace(),
Expand Down
308 changes: 308 additions & 0 deletions kubernetes/resource_kubernetes_cron_job.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,308 @@
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/api/batch/v2alpha1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
pkgApi "k8s.io/apimachinery/pkg/types"
)

const cronJobResourceGroupName = "cronjobs"

var cronJobAPIGroups = []APIGroup{batchV1beta1, batchV2alpha1}

var cronJobNotSupportedError = fmt.Errorf("could not find Kubernetes API group that supports CronJob resources")

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 {
kp := meta.(*kubernetesProvider)
conn := kp.conn

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

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

created := &v1beta1.CronJob{}

log.Printf("[INFO] Creating new cron job: %#v", job)
apiGroup, err := kp.highestSupportedAPIGroup(cronJobResourceGroupName, cronJobAPIGroups...)
if err != nil {
return err
}
switch apiGroup {
case batchV1beta1:
created, err = conn.BatchV1beta1().CronJobs(metadata.Namespace).Create(&job)

case batchV2alpha1:
beta := &v2alpha1.CronJob{}
Convert(job, beta)
out, err2 := conn.BatchV2alpha1().CronJobs(metadata.Namespace).Create(beta)
if err2 != nil {
err = err2
break
}
Convert(out, created)

default:
err = cronJobNotSupportedError
}
if err != nil {
return err
}

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

d.SetId(buildId(created.ObjectMeta))

return resourceKubernetesCronJobRead(d, meta)
}

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

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

ops := patchMetadata("metadata.0.", "/metadata/", d)

if d.HasChange("spec") {
specOps, err := patchCronJobSpec("/spec", "spec.0.", d)
if err != nil {
return err
}
ops = append(ops, specOps...)
}

data, err := ops.MarshalJSON()
if err != nil {
return fmt.Errorf("Failed to marshal update operations: %s", err)
}

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

out := &v1beta1.CronJob{}
apiGroup, err := kp.highestSupportedAPIGroup(cronJobResourceGroupName, cronJobAPIGroups...)
if err != nil {
return err
}
switch apiGroup {
case batchV1beta1:
out, err = conn.BatchV1beta1().CronJobs(namespace).Patch(name, pkgApi.JSONPatchType, data)

case batchV2alpha1:
beta, err2 := conn.BatchV2alpha1().CronJobs(namespace).Patch(name, pkgApi.JSONPatchType, data)
if err2 != nil {
err = err2
break
}
Convert(beta, out)

default:
err = cronJobNotSupportedError
}
if err != nil {
return err
}
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 {
kp := meta.(*kubernetesProvider)

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

log.Printf("[INFO] Reading cron job %s", name)
job, err := readCronJob(kp, namespace, name)
if err != nil {
log.Printf("[DEBUG] Received error: %#v", err)
return err
}
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 {
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 {
kp := meta.(*kubernetesProvider)
conn := kp.conn

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

log.Printf("[INFO] Deleting cron job: %#v", name)
apiGroup, err := kp.highestSupportedAPIGroup(cronJobResourceGroupName, cronJobAPIGroups...)
if err != nil {
return err
}
switch apiGroup {
case batchV1beta1:
err = conn.BatchV1beta1().CronJobs(namespace).Delete(name, nil)

case batchV2alpha1:
err = conn.BatchV2alpha1().CronJobs(namespace).Delete(name, nil)

default:
err = cronJobNotSupportedError
}
if err != nil {
return err
}

err = resource.Retry(1*time.Minute, func() *resource.RetryError {
_, err := readCronJob(kp, namespace, name)
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) {
kp := meta.(*kubernetesProvider)

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

log.Printf("[INFO] Checking cron job %s", name)
_, err = readCronJob(kp, namespace, name)
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
}

func readCronJob(kp *kubernetesProvider, namespace, name string) (cj *v1beta1.CronJob, err error) {
conn := kp.conn

log.Printf("[INFO] Reading CronJob %s", name)
cj = &v1beta1.CronJob{}

apiGroup, err := kp.highestSupportedAPIGroup(cronJobResourceGroupName, cronJobAPIGroups...)
if err != nil {
return nil, err
}
log.Printf("[INFO] Reading CronJob using %s API Group", apiGroup)

switch apiGroup {
case batchV1beta1:
cj, err = conn.BatchV1beta1().CronJobs(namespace).Get(name, metav1.GetOptions{})
return cj, err

case batchV2alpha1:
out, err := conn.BatchV2alpha1().CronJobs(namespace).Get(name, metav1.GetOptions{})
if err != nil {
return nil, err
}
err = Convert(out, cj)

default:
return nil, cronJobNotSupportedError
}

return cj, err
}
Loading

0 comments on commit 1d80a8f

Please sign in to comment.