// Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"). You may not // use this file except in compliance with the License. A copy of the // License is located at // // http://aws.amazon.com/apache2.0/ // // or in the "license" file accompanying this file. This file 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 lrpminvoker contains implementation of lrpm-invoker plugin. (lrpm - long running plugin manager) // lrpminvoker is an ondemand worker plugin - which can be called by SSM config or SSM Command. package lrpminvoker import ( "fmt" "github.com/aws/amazon-ssm-agent/agent/context" "github.com/aws/amazon-ssm-agent/agent/contracts" "github.com/aws/amazon-ssm-agent/agent/framework/processor/executer/iohandler" "github.com/aws/amazon-ssm-agent/agent/jsonutil" "github.com/aws/amazon-ssm-agent/agent/log/logger" "github.com/aws/amazon-ssm-agent/agent/task" ) // Plugin is the type for the lrpm invoker plugin. type Plugin struct { context context.T lrpName string } // LongRunningPluginSettings represents startType configuration of long running plugin type LongRunningPluginSettings struct { StartType string } // InvokerInput represents input to lrpm invoker type InvokerInput struct { ID string `json:"id"` Properties interface{} `json:"properties"` } //todo: add interfaces & dependencies to simplify testing for all calls from lrpminvoker calls to lrpm // NewPlugin returns an instance of lrpminvoker for a given long running plugin name func NewPlugin(context context.T, lrpName string) (*Plugin, error) { //name of the long running plugin that this instance of lrpminvoker interacts with - this is the name the lrpminvoker plugin instance is registered under return &Plugin{ context: context, lrpName: lrpName, }, nil } func (p *Plugin) Execute(config contracts.Configuration, cancelFlag task.CancelFlag, output iohandler.IOHandler) { log := p.context.Log() log.Infof("long running plugin invoker has been invoked") var err error //check if plugin is enabled or not - which would be stored in settings if configJson, ok := config.Properties.(string); ok { log.Debugf("Received plugin configuration - Setting: %s\n Properties: %s\n "+ "OrchestrationDirectory: %s\n MessageId: %s\n BookKeepingFileName: %s\n PluginName: %s\n PluginID: %s\n DefaultWorkingDirectory: %s", config.Settings, logger.PrintCWConfig(configJson, log), config.OrchestrationDirectory, config.MessageId, config.BookKeepingFileName, config.PluginName, config.PluginID, config.DefaultWorkingDirectory) } //load settings from plugin input -> for more details refer to AWS-ConfigureCloudWatch var setting LongRunningPluginSettings if err = jsonutil.Remarshal(config.Settings, &setting); err != nil { log.Errorf(fmt.Sprintf("Invalid format in plugin configuration - %v;\nError %v", config.Settings, err)) p.CreateResult(fmt.Sprintf("Unable to parse Settings for %s", p.lrpName), contracts.ResultStatusFailed, output) return } if cancelFlag.ShutDown() { output.MarkAsShutdown() } else if cancelFlag.Canceled() { output.MarkAsCancelled() } else { property := p.prepareForStart(config, cancelFlag, output) output.SetOutput(property) output.AppendInfo(setting.StartType) } return } // CreateResult returns a PluginResult for given message and status func (p *Plugin) CreateResult(msg string, status contracts.ResultStatus, out iohandler.IOHandler) { if status == contracts.ResultStatusFailed { out.AppendError(msg) out.SetExitCode(1) } else { out.SetExitCode(0) } out.SetStatus(status) return } // prepareForStart remalshal the Property and stop the plug if it was running before. func (p *Plugin) prepareForStart(config contracts.Configuration, cancelFlag task.CancelFlag, output iohandler.IOHandler) (property string) { log := p.context.Log() // track if the preparation process succeed. var err error prop := config.Properties switch prop.(type) { // cloudwatch triggered by run command case string: break // cloudwatch triggered by create association case *string: temp := prop.(*string) prop = *temp break // cloudwatch triggered by association document default: var inputs InvokerInput if err = jsonutil.Remarshal(config.Properties, &inputs); err != nil { log.Errorf(fmt.Sprintf("Invalid format in plugin configuration - %v;\nError %v", config.Properties, err)) p.CreateResult(fmt.Sprintf("Invalid format in plugin configuration - expecting property as string - %s", config.Properties), contracts.ResultStatusFailed, output) return } log.Debug(inputs) // If the docuemnt type is 2.0, there is no Properties field in the docuemnt. // The whole config.Properties is the Properties we want. // So just need to marshal the whole Properties if inputs.Properties == nil { inputs.Properties = config.Properties } if prop, err = jsonutil.Marshal(inputs.Properties); err != nil { log.Error("Cannot marshal properties, ", err) } } // config.Properties if err = jsonutil.Remarshal(prop, &property); err != nil { log.Errorf(fmt.Sprintf("Invalid format in plugin configuration - %v;\nError %v", config.Properties, err)) p.CreateResult(fmt.Sprintf("Invalid format in plugin configuration - expecting property as string - %s", config.Properties), contracts.ResultStatusFailed, output) return } p.CreateResult("success", contracts.ResultStatusSuccess, output) return }