// Copyright 2016-2024, Pulumi Corporation. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package asset import ( "regexp" "strings" "github.com/pulumi/pulumi/sdk/v3/go/common/resource" ) var ( functionRegexp = regexp.MustCompile(`function __.*`) withRegexp = regexp.MustCompile(` with\({ .* }\) {`) environmentRegexp = regexp.MustCompile(` }\).apply\(.*\).apply\(this, arguments\);`) preambleRegexp = regexp.MustCompile( `function __.*\(\) {\n return \(function\(\) {\n with \(__closure\) {\n\nreturn `) postambleRegexp = regexp.MustCompile( `;\n\n }\n }\).apply\(__environment\).apply\(this, arguments\);\n}`) ) // IsUserProgramCode checks to see if this is the special asset containing the users's code func IsUserProgramCode(a *resource.Asset) bool { if !a.IsText() { return false } text := a.Text return functionRegexp.MatchString(text) && withRegexp.MatchString(text) && environmentRegexp.MatchString(text) } // MassageIfUserProgramCodeAsset takes the text for a function and cleans it up a bit to make the // user visible diffs less noisy. Specifically: // 1. it tries to condense things by changling multiple blank lines into a single blank line. // 2. it normalizs the sha hashes we emit so that changes to them don't appear in the diff. // 3. it elides the with-capture headers, as changes there are not generally meaningful. // // TODO(https://github.com/pulumi/pulumi/issues/592) this is baking in a lot of knowledge about // pulumi serialized functions. We should try to move to an alternative mode that isn't so brittle. // Options include: // 1. Have a documented delimeter format that plan.go will look for. Have the function serializer // emit those delimeters around code that should be ignored. // 2. Have our resource generation code supply not just the resource, but the "user presentable" // resource that cuts out a lot of cruft. We could then just diff that content here. func MassageIfUserProgramCodeAsset(asset *resource.Asset, debug bool) *resource.Asset { if debug { return asset } // Only do this for strings that match our serialized function pattern. if !IsUserProgramCode(asset) { return asset } text := asset.Text replaceNewlines := func() { for { newText := strings.ReplaceAll(text, "\n\n\n", "\n\n") if len(newText) == len(text) { break } text = newText } } replaceNewlines() firstFunc := functionRegexp.FindStringIndex(text) text = text[firstFunc[0]:] text = withRegexp.ReplaceAllString(text, " with (__closure) {") text = environmentRegexp.ReplaceAllString(text, " }).apply(__environment).apply(this, arguments);") text = preambleRegexp.ReplaceAllString(text, "") text = postambleRegexp.ReplaceAllString(text, "") replaceNewlines() return &resource.Asset{Text: text} }