// Our intent here is to look for array of commands which will be executed as a part of this document and replace the incompatible code.
func removeDependencyOnInstanceMetadataForManagedInstance(context context.T, parsedMessage messageContracts.SendCommandPayload) (messageContracts.SendCommandPayload, error) {
	log := context.Log()
	var properties []interface{}
	var parsedDocumentProperties ManagedInstanceDocumentProperties

	err := jsonutil.Remarshal(parsedMessage.DocumentContent.RuntimeConfig[appconfig.PluginNameAwsRunScript].Properties, &properties)
	if err != nil {
		log.Errorf("Invalid format of properties in %v document. error: %v", parsedMessage.DocumentName, err)
		return parsedMessage, err
	}

	// Since 'Properties' is an array and we use only one property block for the above documents, array location '0' of 'Properties' is used.
	err = jsonutil.Remarshal(properties[0], &parsedDocumentProperties)
	if err != nil {
		log.Errorf("Invalid format of properties in %v document. error: %v", parsedMessage.DocumentName, err)
		return parsedMessage, err
	}

	region, err := platform.Region()
	if err != nil {
		log.Errorf("Error retrieving agent region. error: %v", err)
		return parsedMessage, err
	}

	// Comment or replace the incompatible code from this document.
	log.Info("Replacing managed instance incompatible code for AWS SSM Document.")
	for i, command := range parsedDocumentProperties.RunCommand {
		if strings.Contains(command, "$metadataLocation = 'http://169.254.169.254/latest/dynamic/instance-identity/document/region'") {
			parsedDocumentProperties.RunCommand[i] = strings.Replace(command, "$metadataLocation = 'http://169.254.169.254/latest/dynamic/instance-identity/document/region'", "# $metadataLocation = 'http://169.254.169.254/latest/dynamic/instance-identity/document/region' (This is done to make it managed instance compatible)", 1)
		}

		if strings.Contains(command, "$metadata = (New-Object Net.WebClient).DownloadString($metadataLocation)") {
			parsedDocumentProperties.RunCommand[i] = strings.Replace(command, "$metadata = (New-Object Net.WebClient).DownloadString($metadataLocation)", "# $metadata = (New-Object Net.WebClient).DownloadString($metadataLocation) (This is done to make it managed instance compatible)", 1)
		}

		if strings.Contains(command, "$region = (ConvertFrom-JSON $metadata).region") {
			parsedDocumentProperties.RunCommand[i] = strings.Replace(command, "$region = (ConvertFrom-JSON $metadata).region", "$region = '"+region+"'", 1)
		}
	}

	// Plug-in the compatible 'Properties' block back to the document.
	properties[0] = parsedDocumentProperties
	var documentProperties interface{} = properties
	parsedMessage.DocumentContent.RuntimeConfig[appconfig.PluginNameAwsRunScript].Properties = documentProperties

	// For debug purposes.
	parsedMessageContent, err := jsonutil.Marshal(parsedMessage)
	if err != nil {
		log.Errorf("Error marshalling %v document. error: %v", parsedMessage.DocumentName, err)
		return parsedMessage, err
	}
	log.Debug("ParsedMessage after removing dependency on instance metadata for managed instance is ", jsonutil.Indent(parsedMessageContent))
	return parsedMessage, nil
}
Esempio n. 2
0
// testRunCommands tests the runCommands or the runCommandsRawInput method for one testcase.
func testRunCommands(t *testing.T, testCase TestCase, rawInput bool) {
	logger.On("Error", mock.Anything).Return(nil)
	logger.Infof("test run commands %v", testCase)
	runCommandTester := func(p *Plugin, mockCancelFlag *task.MockCancelFlag, mockExecuter *executers.MockCommandExecuter, mockS3Uploader *pluginutil.MockDefaultPlugin) {
		// set expectations
		setExecuterExpectations(mockExecuter, testCase, mockCancelFlag, p)
		setS3UploaderExpectations(mockS3Uploader, testCase, p)

		// call method under test
		var res contracts.PluginOutput
		if rawInput {
			// prepare plugin input
			var rawPluginInput interface{}
			err := jsonutil.Remarshal(testCase.Input, &rawPluginInput)
			assert.Nil(t, err)

			res = p.runCommandsRawInput(logger, rawPluginInput, orchestrationDirectory, mockCancelFlag, s3BucketName, s3KeyPrefix)
		} else {
			res = p.runCommands(logger, testCase.Input, orchestrationDirectory, mockCancelFlag, s3BucketName, s3KeyPrefix)
		}

		// assert output is correct (mocked object expectations are tested automatically by testExecution)
		assert.Equal(t, testCase.Output, res)
	}

	testExecution(t, runCommandTester)
}
Esempio n. 3
0
// runCommandsRawInput executes one set of commands and returns their output.
// The input is in the default json unmarshal format (e.g. map[string]interface{}).
func (p *Plugin) runCommandsRawInput(log log.T, rawPluginInput interface{}, orchestrationDirectory string, cancelFlag task.CancelFlag, outputS3BucketName string, outputS3KeyPrefix string) (out ApplicationPluginOutput) {
	var pluginInput ApplicationPluginInput
	err := jsonutil.Remarshal(rawPluginInput, &pluginInput)
	log.Debugf("Plugin input %v", pluginInput)
	if err != nil {
		errorString := fmt.Errorf("Invalid format in plugin properties %v;\nerror %v", rawPluginInput, err)
		out.MarkAsFailed(log, errorString)
		return
	}
	return p.runCommands(log, pluginInput, orchestrationDirectory, cancelFlag, outputS3BucketName, outputS3KeyPrefix)
}
Esempio n. 4
0
// runCommandsRawInput executes one set of commands and returns their output.
// The input is in the default json unmarshal format (e.g. map[string]interface{}).
func (p *Plugin) runCommandsRawInput(log log.T, rawPluginInput interface{}, orchestrationDirectory string, cancelFlag task.CancelFlag, outputS3BucketName string, outputS3KeyPrefix string) (out contracts.PluginOutput) {
	var pluginInput RunCommandPluginInput
	err := jsonutil.Remarshal(rawPluginInput, &pluginInput)
	if err != nil {
		errorString := fmt.Sprintf("Invalid format in plugin properties %v;\nerror %v", rawPluginInput, err)
		out.Errors = append(out.Errors, errorString)
		out.Status = contracts.ResultStatusFailed
		log.Error(errorString)
		return
	}
	return p.runCommands(log, pluginInput, orchestrationDirectory, cancelFlag, outputS3BucketName, outputS3KeyPrefix)
}
Esempio n. 5
0
// LoadParametersAsList returns properties as a list and appropriate PluginResult if error is encountered
func LoadParametersAsList(log log.T, prop interface{}) ([]interface{}, contracts.PluginResult) {

	var properties []interface{}
	var res contracts.PluginResult

	if err := jsonutil.Remarshal(prop, &properties); err != nil {
		log.Errorf("unable to parse plugin configuration")
		res.Output = "Execution failed because agent is unable to parse plugin configuration"
		res.Code = 1
		res.Status = contracts.ResultStatusFailed
	}

	return properties, res
}
Esempio n. 6
0
// testExecute tests the run command plugin's Execute method.
func testExecute(t *testing.T, testCase TestCase) {
	executeTester := func(p *Plugin, mockCancelFlag *task.MockCancelFlag, mockExecuter *executers.MockCommandExecuter, mockS3Uploader *pluginutil.MockDefaultPlugin) {
		// setup expectations and correct outputs
		var pluginProperties []interface{}
		var correctOutputs string
		mockContext := context.NewMockDefault()

		// set expectations
		setCancelFlagExpectations(mockCancelFlag)
		setExecuterExpectations(mockExecuter, testCase, mockCancelFlag, p)
		setS3UploaderExpectations(mockS3Uploader, testCase, p)

		// prepare plugin input
		var rawPluginInput interface{}
		err := jsonutil.Remarshal(testCase.Input, &rawPluginInput)
		assert.Nil(t, err)

		pluginProperties = append(pluginProperties, rawPluginInput)
		correctOutputs = testCase.Output.String()

		//Create messageId which is in the format of aws.ssm.<commandID>.<InstanceID>
		commandID := uuid.NewV4().String()

		// call plugin
		res := p.Execute(
			mockContext,
			contracts.Configuration{
				Properties:             pluginProperties,
				OutputS3BucketName:     s3BucketName,
				OutputS3KeyPrefix:      s3KeyPrefix,
				OrchestrationDirectory: orchestrationDirectory,
				BookKeepingFileName:    commandID,
			}, mockCancelFlag)

		// assert output is correct (mocked object expectations are tested automatically by testExecution)
		assert.NotNil(t, res.StartDateTime)
		assert.NotNil(t, res.EndDateTime)
		assert.Equal(t, correctOutputs, res.Output)

		// assert that the flag is checked after every set of commands
		mockCancelFlag.AssertNumberOfCalls(t, "Canceled", 1)
	}

	testExecution(t, executeTester)
}
Esempio n. 7
0
// updateAgent downloads the installation packages and update the agent
func runUpdateAgent(
	p *Plugin,
	config contracts.Configuration,
	log log.T,
	manager pluginHelper,
	util updateutil.T,
	rawPluginInput interface{},
	cancelFlag task.CancelFlag,
	outputS3BucketName string,
	outputS3KeyPrefix string,
	startTime time.Time) (out UpdatePluginOutput) {
	var pluginInput UpdatePluginInput
	var err error
	var context *updateutil.InstanceContext

	if isUpdateSupported, err := util.IsPlatformSupportedForUpdate(log); err == nil && !isUpdateSupported {
		out.Failed(log, fmt.Errorf("Unsupported platform for update"))
		return
	}

	if err = jsonutil.Remarshal(rawPluginInput, &pluginInput); err != nil {
		out.Failed(log,
			fmt.Errorf("invalid format in plugin properties %v;\nerror %v", rawPluginInput, err))
		return
	}

	if context, err = util.CreateInstanceContext(log); err != nil {
		out.Failed(log, err)
		return
	}

	//Use default manifest location is the override is not present
	if len(pluginInput.Source) == 0 {
		pluginInput.Source = p.ManifestLocation
	}
	//Calculate manifest location base on current instance's region
	pluginInput.Source = strings.Replace(pluginInput.Source, updateutil.RegionHolder, context.Region, -1)
	//Calculate updater package name base on agent name
	pluginInput.UpdaterName = pluginInput.AgentName + updateutil.UpdaterPackageNamePrefix
	//Generate update output
	targetVersion := pluginInput.TargetVersion
	if len(targetVersion) == 0 {
		targetVersion = "latest"
	}
	out.AppendInfo(log, "Updating %v from %v to %v",
		pluginInput.AgentName,
		version.Version,
		targetVersion)

	//Download manifest file
	manifest, downloadErr := manager.downloadManifest(log, util, &pluginInput, context, &out)
	if downloadErr != nil {
		out.Failed(log, downloadErr)
		return
	}

	//Validate update details
	noNeedToUpdate := false
	if noNeedToUpdate, err = manager.validateUpdate(log, &pluginInput, context, manifest, &out); noNeedToUpdate {
		if err != nil {
			out.Failed(log, err)
		}
		return
	}

	//Download updater and retrieve the version number
	updaterVersion := ""
	if updaterVersion, err = manager.downloadUpdater(
		log, util, pluginInput.UpdaterName, manifest, &out, context); err != nil {
		out.Failed(log, err)
		return
	}

	//Generate update command base on the update detail
	cmd := ""
	if cmd, err = manager.generateUpdateCmd(log,
		manifest,
		&pluginInput,
		context,
		updateutil.UpdaterFilePath(appconfig.UpdaterArtifactsRoot, pluginInput.UpdaterName, updaterVersion),
		config.MessageId,
		p.StdoutFileName,
		p.StderrFileName,
		outputS3KeyPrefix,
		outputS3BucketName); err != nil {
		out.Failed(log, err)
		return
	}
	log.Debugf("Update command %v", cmd)

	//Save update plugin result to local file, updater will read it during agent update
	updatePluginResult := &updateutil.UpdatePluginResult{
		StandOut:      out.Stdout,
		StartDateTime: startTime,
	}
	if err = util.SaveUpdatePluginResult(log, appconfig.UpdaterArtifactsRoot, updatePluginResult); err != nil {
		out.Failed(log, err)
		return
	}

	// If disk space is not sufficient, fail the update to prevent installation and notify user in output
	// If loading disk space fails, continue to update (agent update is backed by rollback handler)
	log.Infof("Checking available disk space ...")
	if isDiskSpaceSufficient, err := util.IsDiskSpaceSufficientForUpdate(log); err == nil && !isDiskSpaceSufficient {
		out.Failed(log, errors.New("Insufficient available disk space"))
		return
	}

	log.Infof("Start Installation")
	log.Infof("Hand over update process to %v", pluginInput.UpdaterName)
	//Execute updater, hand over the update process
	workDir := updateutil.UpdateArtifactFolder(
		appconfig.UpdaterArtifactsRoot, pluginInput.UpdaterName, updaterVersion)

	if err = util.ExeCommand(
		log,
		cmd,
		workDir,
		appconfig.UpdaterArtifactsRoot,
		p.StdoutFileName,
		p.StderrFileName,
		true); err != nil {
		out.Failed(log, err)
		return
	}

	out.Pending()
	return
}