// Copyright 2016-2019, 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 backend import ( "context" "fmt" "path" "time" "github.com/rjeczalik/notify" "github.com/pulumi/pulumi/pkg/v2/backend/display" "github.com/pulumi/pulumi/pkg/v2/operations" "github.com/pulumi/pulumi/sdk/v2/go/common/apitype" "github.com/pulumi/pulumi/sdk/v2/go/common/diag/colors" "github.com/pulumi/pulumi/sdk/v2/go/common/util/logging" "github.com/pulumi/pulumi/sdk/v2/go/common/util/result" ) // Watch watches the project's working directory for changes and automatically updates the active // stack. func Watch(ctx context.Context, b Backend, stack Stack, op UpdateOperation, apply Applier) result.Result { opts := ApplierOptions{ DryRun: false, ShowLink: false, } startTime := time.Now() go func() { shown := map[operations.LogEntry]bool{} for { logs, err := b.GetLogs(ctx, stack, op.StackConfiguration, operations.LogQuery{ StartTime: &startTime, }) if err != nil { logging.V(5).Infof("failed to get logs: %v", err.Error()) } for _, logEntry := range logs { if _, shownAlready := shown[logEntry]; !shownAlready { eventTime := time.Unix(0, logEntry.Timestamp*1000000) display.PrintfWithWatchPrefix(eventTime, logEntry.ID, "%s\n", logEntry.Message) shown[logEntry] = true } } time.Sleep(10 * time.Second) } }() events := make(chan notify.EventInfo, 1) if err := notify.Watch(path.Join(op.Root, "..."), events, notify.All); err != nil { return result.FromError(err) } defer notify.Stop(events) fmt.Printf(op.Opts.Display.Color.Colorize( colors.SpecHeadline+"Watching (%s):"+colors.Reset+"\n"), stack.Ref()) for range events { display.PrintfWithWatchPrefix(time.Now(), "", op.Opts.Display.Color.Colorize(colors.SpecImportant+"Updating..."+colors.Reset+"\n")) // Perform the update operation _, res := apply(ctx, apitype.UpdateUpdate, stack, op, opts, nil) if res != nil { logging.V(5).Infof("watch update failed: %v", res.Error()) if res.Error() == context.Canceled { return res } display.PrintfWithWatchPrefix(time.Now(), "", op.Opts.Display.Color.Colorize(colors.SpecImportant+"Update failed."+colors.Reset+"\n")) } else { display.PrintfWithWatchPrefix(time.Now(), "", op.Opts.Display.Color.Colorize(colors.SpecImportant+"Update complete."+colors.Reset+"\n")) } } return nil }