Skip to content

Commit

Permalink
Implement Server-Side Apply mode
Browse files Browse the repository at this point in the history
Add a mode to the provider that uses Server-Side Apply (SSA) for resource management. See #2011 for additional details on the background and design of this feature.
  • Loading branch information
lblackstone committed Jun 16, 2022
1 parent 605e31c commit 7fd8440
Show file tree
Hide file tree
Showing 10 changed files with 281 additions and 72 deletions.
21 changes: 18 additions & 3 deletions provider/cmd/pulumi-resource-kubernetes/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -365,13 +365,18 @@
},
"enableDryRun": {
"type": "boolean",
"description": "BETA FEATURE - If present and set to true, enable server-side diff calculations.\nThis feature is in developer preview, and is disabled by default.\n\nThis config can be specified in the following ways, using this precedence:\n1. This `enableDryRun` parameter.\n2. The `PULUMI_K8S_ENABLE_DRY_RUN` environment variable."
"description": "Deprecated. If present and set to true, enable server-side diff calculations.\n",
"deprecationMessage": "This option has been replaced by `enableServerSideApply`."
},
"enableReplaceCRD": {
"type": "boolean",
"description": "Obsolete. This option has no effect.",
"deprecationMessage": "This option is deprecated, and will be removed in a future release."
},
"enableServerSideApply": {
"type": "boolean",
"description": "BETA FEATURE - If present and set to true, enable Server-Side Apply mode.\nSee https://github.com/pulumi/pulumi-kubernetes/issues/2011 for additional details.\nThis feature is in developer preview, and is disabled by default."
},
"kubeconfig": {
"type": "string",
"description": "The contents of a kubeconfig file or the path to a kubeconfig file. If this is set, this config will be used instead of $KUBECONFIG.",
Expand Down Expand Up @@ -31921,12 +31926,13 @@
},
"enableDryRun": {
"type": "boolean",
"description": "BETA FEATURE - If present and set to true, enable server-side diff calculations.\nThis feature is in developer preview, and is disabled by default.",
"description": "Deprecated. If present and set to true, enable server-side diff calculations.\n",
"defaultInfo": {
"environment": [
"PULUMI_K8S_ENABLE_DRY_RUN"
]
}
},
"deprecationMessage": "This option has been replaced by `enableServerSideApply`."
},
"enableReplaceCRD": {
"type": "boolean",
Expand All @@ -31938,6 +31944,15 @@
},
"deprecationMessage": "This option is deprecated, and will be removed in a future release."
},
"enableServerSideApply": {
"type": "boolean",
"description": "BETA FEATURE - If present and set to true, enable Server-Side Apply mode.\nSee https://github.com/pulumi/pulumi-kubernetes/issues/2011 for additional details.\nThis feature is in developer preview, and is disabled by default.",
"defaultInfo": {
"environment": [
"PULUMI_K8S_ENABLE_SERVER_SIDE_APPLY"
]
}
},
"helmReleaseSettings": {
"$ref": "#/types/kubernetes:index:HelmReleaseSettings",
"description": "Options to configure the Helm Release resource."
Expand Down
112 changes: 85 additions & 27 deletions provider/pkg/await/await.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2016-2021, Pulumi Corporation.
// Copyright 2016-2022, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -17,6 +17,7 @@ package await
import (
"context"
"fmt"
"strings"

"github.com/pulumi/pulumi-kubernetes/provider/v3/pkg/clients"
"github.com/pulumi/pulumi-kubernetes/provider/v3/pkg/cluster"
Expand All @@ -35,9 +36,11 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/dynamic"
k8sopenapi "k8s.io/kubectl/pkg/util/openapi"
"sigs.k8s.io/yaml"
)

// --------------------------------------------------------------------------
Expand All @@ -58,6 +61,7 @@ type ProviderConfig struct {
URN resource.URN
InitialAPIVersion string
ClusterVersion *cluster.ServerVersion
ServerSideApply bool

ClientSet *clients.DynamicClientSet
DedupLogger *logging.DedupLogger
Expand Down Expand Up @@ -177,16 +181,31 @@ func Creation(c CreateConfig) (*unstructured.Unstructured, error) {
}
}

outputs, err = client.Create(context.TODO(), c.Inputs, options)
if err != nil {
// If the namespace hasn't been created yet, the preview will always fail.
if c.DryRun && IsNamespaceNotFoundErr(err) {
return &namespaceError{c.Inputs}
if c.ServerSideApply {
options := metav1.PatchOptions{
FieldManager: "pulumi-resource-kubernetes",
}
objYAML, err := yaml.Marshal(c.Inputs.Object)
if err != nil {
return err
}
outputs, err = client.Patch(
context.TODO(), c.Inputs.GetName(), types.ApplyPatchType, objYAML, options)
if err != nil {
return err
}
} else {
outputs, err = client.Create(context.TODO(), c.Inputs, options)
if err != nil {
// If the namespace hasn't been created yet, the preview will always fail.
if c.DryRun && IsNamespaceNotFoundErr(err) {
return &namespaceError{c.Inputs}
}

_ = c.Host.LogStatus(c.Context, diag.Info, c.URN, fmt.Sprintf(
"Retry #%d; creation failed: %v", i, err))
return err
_ = c.Host.LogStatus(c.Context, diag.Info, c.URN, fmt.Sprintf(
"Retry #%d; creation failed: %v", i, err))
return err
}
}

return nil
Expand Down Expand Up @@ -368,9 +387,6 @@ func Update(c UpdateConfig) (*unstructured.Unstructured, error) {

var currentOutputs *unstructured.Unstructured
if clients.IsCRD(c.Inputs) {
// Note: This feature is currently enabled with a provider feature flag, but is expected to eventually become
// the default behavior.

// CRDs require special handling to update. Rather than computing a patch, replace the CRD with a PUT
// operation (equivalent to running `kubectl replace`). This is accomplished by getting the `resourceVersion`
// of the existing CRD, setting that as the `resourceVersion` in the request, and then running an update. This
Expand All @@ -382,23 +398,46 @@ func Update(c UpdateConfig) (*unstructured.Unstructured, error) {
return nil, err
}
} else {
// Create merge patch (prefer strategic merge patch, fall back to JSON merge patch).
patch, patchType, _, err := openapi.PatchForResourceUpdate(c.Resources, c.Previous, c.Inputs, liveOldObj)
if err != nil {
return nil, err
}
if c.ServerSideApply {
objYAML, err := yaml.Marshal(c.Inputs.Object)
if err != nil {
return nil, err
}
var options metav1.PatchOptions
if c.DryRun {
options.DryRun = []string{metav1.DryRunAll}
}
options.FieldManager = "pulumi-resource-kubernetes"
//force := true
//options.Force = &force

// Issue patch request.
// NOTE: We can use the same client because if the `kind` changes, this will cause
// a replace (i.e., destroy and create).
currentOutputs, err = client.Patch(context.TODO(), c.Inputs.GetName(), types.ApplyPatchType, objYAML, options)
if err != nil {
return nil, err
}

var options metav1.PatchOptions
if c.DryRun {
options.DryRun = []string{metav1.DryRunAll}
}
} else {
// Create merge patch (prefer strategic merge patch, fall back to JSON merge patch).
patch, patchType, _, err := openapi.PatchForResourceUpdate(c.Resources, c.Previous, c.Inputs, liveOldObj)
if err != nil {
return nil, err
}

// Issue patch request.
// NOTE: We can use the same client because if the `kind` changes, this will cause
// a replace (i.e., destroy and create).
currentOutputs, err = client.Patch(context.TODO(), c.Inputs.GetName(), patchType, patch, options)
if err != nil {
return nil, err
var options metav1.PatchOptions
if c.DryRun {
options.DryRun = []string{metav1.DryRunAll}
}

// Issue patch request.
// NOTE: We can use the same client because if the `kind` changes, this will cause
// a replace (i.e., destroy and create).
currentOutputs, err = client.Patch(context.TODO(), c.Inputs.GetName(), patchType, patch, options)
if err != nil {
return nil, err
}
}
}
if err != nil {
Expand Down Expand Up @@ -484,6 +523,25 @@ func Deletion(c DeleteConfig) error {
return nilIfGVKDeleted(err)
}

patchResource := strings.HasSuffix(c.URN.Type().String(), "Patch")
if c.ServerSideApply && patchResource {
obj := unstructured.Unstructured{}
obj.SetAPIVersion(c.Inputs.GetAPIVersion())
obj.SetKind(c.Inputs.GetKind())
obj.SetNamespace(c.Inputs.GetNamespace())
obj.SetName(c.Inputs.GetName())
yamlObj, err := yaml.Marshal(obj.Object)
if err != nil {
return err
}

_, err = client.Patch(context.TODO(), c.Name, types.ApplyPatchType, yamlObj, metav1.PatchOptions{
FieldManager: "pulumi-resource-kubernetes",
//FieldValidation: metav1.FieldValidationIgnore,
})
return err
}

timeout := metadata.TimeoutDuration(c.Timeout, c.Inputs, 300)
timeoutSeconds := int64(timeout.Seconds())
listOpts := metav1.ListOptions{
Expand Down
19 changes: 17 additions & 2 deletions provider/pkg/gen/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,12 @@ func PulumiSchema(swagger map[string]interface{}) pschema.PackageSpec {
TypeSpec: pschema.TypeSpec{Type: "string"},
},
"enableDryRun": {
Description: "BETA FEATURE - If present and set to true, enable server-side diff calculations.\nThis feature is in developer preview, and is disabled by default.\n\nThis config can be specified in the following ways, using this precedence:\n1. This `enableDryRun` parameter.\n2. The `PULUMI_K8S_ENABLE_DRY_RUN` environment variable.",
Description: "Deprecated. If present and set to true, enable server-side diff calculations.\n",
TypeSpec: pschema.TypeSpec{Type: "boolean"},
DeprecationMessage: "This option has been replaced by `enableServerSideApply`.",
},
"enableServerSideApply": {
Description: "BETA FEATURE - If present and set to true, enable Server-Side Apply mode.\nSee https://github.com/pulumi/pulumi-kubernetes/issues/2011 for additional details.\nThis feature is in developer preview, and is disabled by default.",
TypeSpec: pschema.TypeSpec{Type: "boolean"},
},
"enableReplaceCRD": {
Expand Down Expand Up @@ -134,7 +139,17 @@ func PulumiSchema(swagger map[string]interface{}) pschema.PackageSpec {
"PULUMI_K8S_ENABLE_DRY_RUN",
},
},
Description: "BETA FEATURE - If present and set to true, enable server-side diff calculations.\nThis feature is in developer preview, and is disabled by default.",
Description: "Deprecated. If present and set to true, enable server-side diff calculations.\n",
TypeSpec: pschema.TypeSpec{Type: "boolean"},
DeprecationMessage: "This option has been replaced by `enableServerSideApply`.",
},
"enableServerSideApply": {
DefaultInfo: &pschema.DefaultSpec{
Environment: []string{
"PULUMI_K8S_ENABLE_SERVER_SIDE_APPLY",
},
},
Description: "BETA FEATURE - If present and set to true, enable Server-Side Apply mode.\nSee https://github.com/pulumi/pulumi-kubernetes/issues/2011 for additional details.\nThis feature is in developer preview, and is disabled by default.",
TypeSpec: pschema.TypeSpec{Type: "boolean"},
},
"enableReplaceCRD": {
Expand Down
Loading

0 comments on commit 7fd8440

Please sign in to comment.