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

Implement Server-Side Apply mode #2029

Merged
merged 7 commits into from
Jun 23, 2022
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: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
## Unreleased

- Implement Server-Side Apply mode (https://github.com/pulumi/pulumi-kubernetes/pull/2029)
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe add some more details on how to enable this. (And mark it preview etc.)

Copy link
Member Author

Choose a reason for hiding this comment

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

I'll add that in a followup PR. This isn't really feature-complete yet, so I'm not quite ready to encourage usage.


## 3.19.4 (June 21, 2022)

- Use fully-qualified resource name for generating manifests, to avoid conflicts (https://github.com/pulumi/pulumi-kubernetes/pull/2007)
Expand Down
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 @@ -369,13 +369,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 @@ -31929,12 +31934,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 @@ -31946,6 +31952,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
115 changes: 84 additions & 31 deletions provider/pkg/await/await.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 @@ -25,6 +26,7 @@ import (
"github.com/pulumi/pulumi-kubernetes/provider/v3/pkg/metadata"
"github.com/pulumi/pulumi-kubernetes/provider/v3/pkg/openapi"
"github.com/pulumi/pulumi-kubernetes/provider/v3/pkg/retry"
"github.com/pulumi/pulumi-kubernetes/provider/v3/pkg/ssa"
pulumiprovider "github.com/pulumi/pulumi/pkg/v3/resource/provider"
"github.com/pulumi/pulumi/sdk/v3/go/common/diag"
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
Expand All @@ -35,9 +37,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 @@ -57,7 +61,9 @@ type ProviderConfig struct {
Host *pulumiprovider.HostClient
URN resource.URN
InitialAPIVersion string
FieldManager string
ClusterVersion *cluster.ServerVersion
ServerSideApply bool

ClientSet *clients.DynamicClientSet
DedupLogger *logging.DedupLogger
Expand Down Expand Up @@ -147,11 +153,6 @@ func Creation(c CreateConfig) (*unstructured.Unstructured, error) {
// nolint
// https://github.com/kubernetes/kubernetes/blob/54889d581a35acf940d52a8a384cccaa0b597ddc/pkg/kubectl/cmd/apply/apply.go#L94

var options metav1.CreateOptions
if c.DryRun {
options.DryRun = []string{metav1.DryRunAll}
}

var outputs *unstructured.Unstructured
var client dynamic.ResourceInterface
err := retry.SleepingRetry(
Expand All @@ -177,16 +178,41 @@ func Creation(c CreateConfig) (*unstructured.Unstructured, error) {
}
}

outputs, err = client.Create(c.Context, 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 {
force := metadata.IsAnnotationTrue(c.Inputs, metadata.AnnotationPatchForce)
options := metav1.PatchOptions{
FieldManager: c.FieldManager,
Force: &force,
}
if c.DryRun {
options.DryRun = []string{metav1.DryRunAll}
}
lblackstone marked this conversation as resolved.
Show resolved Hide resolved
objYAML, err := yaml.Marshal(c.Inputs.Object)
if err != nil {
return err
}
outputs, err = client.Patch(
c.Context, c.Inputs.GetName(), types.ApplyPatchType, objYAML, options)
if err != nil {
return err
}
} else {
var options metav1.CreateOptions
if c.DryRun {
options.DryRun = []string{metav1.DryRunAll}
}

outputs, err = client.Create(c.Context, 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 +394,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
lblackstone marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -382,23 +405,47 @@ 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
}
force := metadata.IsAnnotationTrue(c.Inputs, metadata.AnnotationPatchForce)
options := metav1.PatchOptions{
FieldManager: c.FieldManager,
Force: &force,
}
if c.Preview {
options.DryRun = []string{metav1.DryRunAll}
}

var options metav1.PatchOptions
if c.Preview {
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(c.Context, c.Inputs.GetName(), types.ApplyPatchType, objYAML, options)
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(c.Context, c.Inputs.GetName(), patchType, patch, options)
if err != nil {
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
}

var options metav1.PatchOptions
if c.Preview {
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(c.Context, c.Inputs.GetName(), patchType, patch, options)
if err != nil {
return nil, err
}
}
}
if err != nil {
Expand Down Expand Up @@ -484,6 +531,12 @@ func Deletion(c DeleteConfig) error {
return nilIfGVKDeleted(err)
}

patchResource := strings.HasSuffix(c.URN.Type().String(), "Patch")
if c.ServerSideApply && patchResource {
err = ssa.Relinquish(c.Context, client, c.Inputs, c.FieldManager)
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 @@ -73,7 +73,12 @@ func PulumiSchema(swagger map[string]interface{}) pschema.PackageSpec {
TypeSpec: pschema.TypeSpec{Type: "boolean"},
},
"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 @@ -142,7 +147,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
3 changes: 3 additions & 0 deletions provider/pkg/metadata/annotations.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ const (
AnnotationInitialAPIVersion = AnnotationPrefix + "initialApiVersion"
AnnotationReplaceUnready = AnnotationPrefix + "replaceUnready"

AnnotationPatchForce = AnnotationPrefix + "patchForce"
AnnotationPatchFieldManager = AnnotationPrefix + "patchFieldManager"

AnnotationHelmHook = "helm.sh/hook"
)

Expand Down
Loading