コード例 #1
0
        internal UpdateStackResponse UpdateStack(UpdateStackRequest request)
        {
            var marshaller   = new UpdateStackRequestMarshaller();
            var unmarshaller = UpdateStackResponseUnmarshaller.Instance;

            return(Invoke <UpdateStackRequest, UpdateStackResponse>(request, marshaller, unmarshaller));
        }
コード例 #2
0
        /// <summary>
        /// Initiates the asynchronous execution of the UpdateStack operation.
        /// <seealso cref="Amazon.CloudFormation.IAmazonCloudFormation.UpdateStack"/>
        /// </summary>
        ///
        /// <param name="request">Container for the necessary parameters to execute the UpdateStack operation.</param>
        /// <param name="cancellationToken">
        ///     A cancellation token that can be used by other objects or threads to receive notice of cancellation.
        /// </param>
        /// <returns>The task object representing the asynchronous operation.</returns>
        public Task <UpdateStackResponse> UpdateStackAsync(UpdateStackRequest request, CancellationToken cancellationToken = default(CancellationToken))
        {
            var marshaller   = new UpdateStackRequestMarshaller();
            var unmarshaller = UpdateStackResponseUnmarshaller.GetInstance();

            return(Invoke <IRequest, UpdateStackRequest, UpdateStackResponse>(request, marshaller, unmarshaller, signer, cancellationToken));
        }
コード例 #3
0
        /// <summary>
        /// Initiates the asynchronous execution of the UpdateStack operation.
        /// </summary>
        ///
        /// <param name="request">Container for the necessary parameters to execute the UpdateStack operation.</param>
        /// <param name="cancellationToken">
        ///     A cancellation token that can be used by other objects or threads to receive notice of cancellation.
        /// </param>
        /// <returns>The task object representing the asynchronous operation.</returns>
        public Task <UpdateStackResponse> UpdateStackAsync(UpdateStackRequest request, System.Threading.CancellationToken cancellationToken = default(CancellationToken))
        {
            var marshaller   = new UpdateStackRequestMarshaller();
            var unmarshaller = UpdateStackResponseUnmarshaller.Instance;

            return(InvokeAsync <UpdateStackRequest, UpdateStackResponse>(request, marshaller,
                                                                         unmarshaller, cancellationToken));
        }
コード例 #4
0
        public void FormPutTypeTest()
        {
            var request = new UpdateStackRequest();

            request.StackName   = "test";
            request.StackId     = "test";
            request.ContentType = FormatType.FORM;
            request.BodyParameters.Add("ContentMD5NotMatched", "test");

            var exception = Assert.Throws <ClientException>(() => { client.GetAcsResponse(request); });

            Assert.Equal("HTTPBadRequest", exception.ErrorCode);
        }
コード例 #5
0
        /// <summary>
        /// <para>Updates a stack as specified in the template. After the call completes successfully, the stack update starts. You can check the status
        /// of the stack via the DescribeStacks action.</para> <para> <b>Note: </b> You cannot update <a href="http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket.html">AWS::S3::Bucket</a> resources, for
        /// example, to add or modify tags.</para> <para>To get a copy of the template for an existing stack, you can use the GetTemplate
        /// action.</para> <para>Tags that were associated with this stack during creation time will still be associated with the stack after an
        /// <c>UpdateStack</c> operation.</para> <para>For more information about creating an update template, updating a stack, and monitoring the
        /// progress of the update, see <a href="http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks.html">Updating
        /// a Stack</a> .</para>
        /// </summary>
        ///
        /// <param name="request">Container for the necessary parameters to execute the UpdateStack service method on
        /// AmazonCloudFormation.</param>
        ///
        /// <returns>The response from the UpdateStack service method, as returned by AmazonCloudFormation.</returns>
        ///
        /// <exception cref="T:Amazon.CloudFormation.Model.InsufficientCapabilitiesException" />
        public UpdateStackResponse UpdateStack(UpdateStackRequest request)
        {
            var task = UpdateStackAsync(request);

            try
            {
                return(task.Result);
            }
            catch (AggregateException e)
            {
                ExceptionDispatchInfo.Capture(e.InnerException).Throw();
                return(null);
            }
        }
コード例 #6
0
        /// <summary>
        /// 本接口(UpdateStack)用于更新资源栈的名称和描述。
        /// </summary>
        /// <param name="req"><see cref="UpdateStackRequest"/></param>
        /// <returns><see cref="UpdateStackResponse"/></returns>
        public UpdateStackResponse UpdateStackSync(UpdateStackRequest req)
        {
            JsonResponseModel <UpdateStackResponse> rsp = null;

            try
            {
                var strResp = this.InternalRequestSync(req, "UpdateStack");
                rsp = JsonConvert.DeserializeObject <JsonResponseModel <UpdateStackResponse> >(strResp);
            }
            catch (JsonSerializationException e)
            {
                throw new TencentCloudSDKException(e.Message);
            }
            return(rsp.Response);
        }
コード例 #7
0
        public void FormPutTypeTest()
        {
            var request = new UpdateStackRequest();

            request.StackName   = "test";
            request.StackId     = "test";
            request.ContentType = FormatType.FORM;
            request.BodyParameters.Add("ContentMD5NotMatched", "test");

            var exception = Assert.Throws <ClientException>(() => { client.GetAcsResponse(request); });

            Assert.Equal("HTTPBadRequest", exception.ErrorCode);
            Assert.Equal(
                "The server could not comply with the request since it is either malformed or otherwise incorrect. The content type is None. Try use \"application/json\" instead.",
                exception.ErrorMessage);
        }
コード例 #8
0
        protected override void ProcessRecord()
        {
            base.ProcessRecord();
            UpdateStackRequest request;

            try
            {
                request = new UpdateStackRequest
                {
                    StackId            = StackId,
                    UpdateStackDetails = UpdateStackDetails,
                    OpcRequestId       = OpcRequestId,
                    IfMatch            = IfMatch
                };

                response = client.UpdateStack(request).GetAwaiter().GetResult();
                WriteOutput(response, response.Stack);
                FinishProcessing(response);
            }
            catch (Exception ex)
            {
                TerminatingErrorDuringExecution(ex);
            }
        }
コード例 #9
0
        public static void UpdateStack(string stackName, Uri templateUri)
        {
            AmazonCloudFormationClient client = new AmazonCloudFormationClient(RegionEndpoint.USEast1);

            UpdateStackRequest request = new UpdateStackRequest
            {
                StackName   = stackName,
                TemplateURL = templateUri.AbsoluteUri
            };

            try
            {
                var response = client.UpdateStack(request);

                if (response.HttpStatusCode < HttpStatusCode.OK || response.HttpStatusCode >= HttpStatusCode.MultipleChoices)
                {
                    throw new Exception(response.ToString());
                }
            }
            catch (Amazon.Runtime.Internal.HttpErrorResponseException ex)
            {
                throw new Exception(ex.Message);
            }
        }
コード例 #10
0
        //--- Methods ---
        public async Task <bool> Deploy(Module module, string template, bool allowDataLoss, bool protectStack)
        {
            var stackName = $"{module.Settings.Tier}-{module.Name}";

            Console.WriteLine($"Deploying stack: {stackName}");

            // upload function packages
            var transferUtility = new TransferUtility(module.Settings.S3Client);

            foreach (var function in module.Functions)
            {
                await UploadPackage(
                    module.Settings.DeploymentBucketName,
                    function.PackageS3Key,
                    function.Package,
                    "Lambda function"
                    );
            }

            // upload data packages (NOTE: packages are cannot be nested, so just enumerate the top level parameters)
            foreach (var package in module.Parameters.OfType <PackageParameter>())
            {
                await UploadPackage(
                    module.Settings.DeploymentBucketName,
                    package.PackageS3Key,
                    package.Package,
                    "package"
                    );
            }

            // check if cloudformation stack already exists
            string mostRecentStackEventId = null;

            try {
                var response = await module.Settings.CfClient.DescribeStackEventsAsync(new DescribeStackEventsRequest {
                    StackName = stackName
                });

                var mostRecentStackEvent = response.StackEvents.First();

                // make sure the stack is not already in an update operation
                if (!IsFinalStackEvent(mostRecentStackEvent))
                {
                    module.Settings.AddError("stack appears to be undergoing an update operation");
                    return(false);
                }
                mostRecentStackEventId = mostRecentStackEvent.EventId;
            } catch (AmazonCloudFormationException) { }

            // set optional notification topics for cloudformation operations
            var notificationArns = new List <string>();

            if (module.Settings.NotificationTopicArn != null)
            {
                notificationArns.Add(module.Settings.NotificationTopicArn);
            }

            // upload cloudformation template
            string templateUrl = null;

            if (module.Settings.DeploymentBucketName != null)
            {
                var templateFile   = Path.GetTempFileName();
                var templateSuffix = module.Settings.GitSha ?? ("UTC" + DateTime.UtcNow.ToString("yyyyMMddhhmmss"));
                var templateS3Key  = $"{module.Name}/cloudformation-{templateSuffix}.json";
                templateUrl = $"https://s3.amazonaws.com/{module.Settings.DeploymentBucketName}/{templateS3Key}";
                try {
                    Console.WriteLine($"=> Uploading CloudFormation template: s3://{module.Settings.DeploymentBucketName}/{templateS3Key}");
                    File.WriteAllText(templateFile, template);
                    await transferUtility.UploadAsync(templateFile, module.Settings.DeploymentBucketName, templateS3Key);
                } finally {
                    try {
                        File.Delete(templateFile);
                    } catch { }
                }
            }

            // default stack policy denies all updates
            var stackPolicyBody =
                @"{
    ""Statement"": [{
        ""Effect"": ""Allow"",
        ""Action"": ""Update:*"",
        ""Principal"": ""*"",
        ""Resource"": ""*""
    }, {
        ""Effect"": ""Deny"",
        ""Action"": [
            ""Update:Replace"",
            ""Update:Delete""
        ],
        ""Principal"": ""*"",
        ""Resource"": ""*"",
        ""Condition"": {
            ""StringEquals"": {
                ""ResourceType"": [
                    ""AWS::ApiGateway::RestApi"",
                    ""AWS::AppSync::GraphQLApi"",
                    ""AWS::DynamoDB::Table"",
                    ""AWS::EC2::Instance"",
                    ""AWS::EMR::Cluster"",
                    ""AWS::Kinesis::Stream"",
                    ""AWS::KinesisFirehose::DeliveryStream"",
                    ""AWS::KMS::Key"",
                    ""AWS::Neptune::DBCluster"",
                    ""AWS::Neptune::DBInstance"",
                    ""AWS::RDS::DBInstance"",
                    ""AWS::Redshift::Cluster"",
                    ""AWS::S3::Bucket""
                ]
            }
        }
    }]
}";
            var stackDuringUpdatePolicyBody =
                @"{
    ""Statement"": [{
        ""Effect"": ""Allow"",
        ""Action"": ""Update:*"",
        ""Principal"": ""*"",
        ""Resource"": ""*""
    }]
}";

            // create/update cloudformation stack
            var success = false;

            if (mostRecentStackEventId != null)
            {
                try {
                    Console.WriteLine($"=> Stack update initiated");
                    var request = new UpdateStackRequest {
                        StackName    = stackName,
                        Capabilities = new List <string> {
                            "CAPABILITY_NAMED_IAM"
                        },
                        NotificationARNs            = notificationArns,
                        StackPolicyBody             = stackPolicyBody,
                        StackPolicyDuringUpdateBody = allowDataLoss ? stackDuringUpdatePolicyBody : null,
                        TemplateURL  = templateUrl,
                        TemplateBody = (templateUrl == null) ? template : null
                    };
                    var response = await module.Settings.CfClient.UpdateStackAsync(request);

                    var outcome = await TrackStackUpdate(module, response.StackId, mostRecentStackEventId);

                    if (outcome.Success)
                    {
                        Console.WriteLine($"=> Stack update finished (finished: {DateTime.Now:yyyy-MM-dd HH:mm:ss})");
                        ShowStackResult(outcome.Stack);
                        success = true;
                    }
                    else
                    {
                        Console.WriteLine($"=> Stack update FAILED (finished: {DateTime.Now:yyyy-MM-dd HH:mm:ss})");
                    }
                } catch (AmazonCloudFormationException e) when(e.Message == "No updates are to be performed.")
                {
                    // this error is thrown when no required updates where found
                    Console.WriteLine($"=> No stack update required (finished: {DateTime.Now:yyyy-MM-dd HH:mm:ss})");
                    success = true;
                }
            }
            else
            {
                Console.WriteLine($"=> Stack creation initiated");
                var request = new CreateStackRequest {
                    StackName    = stackName,
                    Capabilities = new List <string> {
                        "CAPABILITY_NAMED_IAM"
                    },
                    OnFailure                   = OnFailure.DELETE,
                    NotificationARNs            = notificationArns,
                    StackPolicyBody             = stackPolicyBody,
                    EnableTerminationProtection = protectStack,
                    TemplateURL                 = templateUrl,
                    TemplateBody                = (templateUrl == null) ? template : null
                };
                var response = await module.Settings.CfClient.CreateStackAsync(request);

                var outcome = await TrackStackUpdate(module, response.StackId, mostRecentStackEventId);

                if (outcome.Success)
                {
                    Console.WriteLine($"=> Stack creation finished (finished: {DateTime.Now:yyyy-MM-dd HH:mm:ss})");
                    ShowStackResult(outcome.Stack);
                    success = true;
                }
                else
                {
                    Console.WriteLine($"=> Stack creation FAILED (finished: {DateTime.Now:yyyy-MM-dd HH:mm:ss})");
                }
            }
            return(success);

            // local function
            void ShowStackResult(Stack stack)
            {
                var outputs = stack.Outputs;

                if (outputs.Any())
                {
                    Console.WriteLine("Stack output values:");
                    foreach (var output in outputs)
                    {
                        Console.WriteLine($"{output.OutputKey}{(output.Description != null ? $" ({output.Description})" : "")}: {output.OutputValue}");
                    }
                }
            }

            async Task UploadPackage(string bucket, string key, string package, string description)
            {
                // check if a matching package file already exists in the bucket
                var found = false;

                try {
                    await module.Settings.S3Client.GetObjectMetadataAsync(new GetObjectMetadataRequest {
                        BucketName = bucket,
                        Key        = key
                    });

                    found = true;
                } catch { }

                // only upload files that don't exist
                if (!found)
                {
                    Console.WriteLine($"=> Uploading {description}: s3://{bucket}/{key} ");
                    await transferUtility.UploadAsync(package, bucket, key);
                }

                // always delete the source zip file when there is no failure
                try {
                    File.Delete(package);
                } catch { }
            }
        }
コード例 #11
0
 public async Task <UpdateStackResponse> UpdateStackAsync(string stackName, string stackId, UpdateStackRequest request)
 {
     AlibabaCloud.TeaUtil.Models.RuntimeOptions runtime = new AlibabaCloud.TeaUtil.Models.RuntimeOptions();
     return(await UpdateStackWithOptionsAsync(stackName, stackId, request, runtime));
 }
コード例 #12
0
 public UpdateStackResponse UpdateStack(string stackName, string stackId, UpdateStackRequest request)
 {
     AlibabaCloud.TeaUtil.Models.RuntimeOptions runtime = new AlibabaCloud.TeaUtil.Models.RuntimeOptions();
     return(UpdateStackWithOptions(stackName, stackId, request, runtime));
 }
コード例 #13
0
 public async Task <UpdateStackResponse> UpdateStackWithOptionsAsync(string stackName, string stackId, UpdateStackRequest request, AlibabaCloud.TeaUtil.Models.RuntimeOptions runtime)
 {
     AlibabaCloud.TeaUtil.Common.ValidateModel(request);
     return(TeaModel.ToObject <UpdateStackResponse>(await DoRequestAsync("2015-09-01", "HTTPS", "PUT", "AK", "/stacks/" + stackName + "/" + stackId, null, request.Headers, null, runtime)));
 }
コード例 #14
0
        public async Task <string> FunctionHandler(S3EventNotification evnt, ILambdaContext context)
        {
            // S3 Client and the associated Call Variables
            IAmazonS3 S3Client = new AmazonS3Client();

            S3EventNotification.S3Entity s3Event = new S3EventNotification.S3Entity();
            GetObjectRequest             ParameterFileRequest  = new GetObjectRequest();
            GetObjectResponse            ParameterFileResponse = new GetObjectResponse();
            GetPreSignedUrlRequest       PresignedKeyRequest   = new GetPreSignedUrlRequest();
            GetObjectResponse            CommitInfoResponse    = new GetObjectResponse();

            // Cloudformation Client and associated Variables
            AmazonCloudFormationClient CloudformationClient  = new AmazonCloudFormationClient();
            List <Parameter>           Parameters            = new List <Parameter>();
            DescribeStacksRequest      CurrentStacksRequest  = new DescribeStacksRequest();
            DescribeStacksResponse     CurrentStacksResponse = new DescribeStacksResponse();

            // A flag to determine if I want to create a new stack or update an existing one
            bool newstack = new bool();

            // The name of the stack which will be generated by the deployment process.
            string TargetStackName;

            // The fixed file name values the deployment process will require. If all three files are not present no deployment will take place.
            string DeploymentFileName = "master.template";
            string ParameterFileName  = "Parameters.txt";
            string CommitInfoFileName = "commitinfo.txt";


            // Write details of the s3 event to my logger
            s3Event = evnt.Records?[0].S3;
            context.Logger.Log("S3 Cloudformation Template Upload Event Recieved. Processing potential deployment.");
            context.Logger.Log("Bucket: " + s3Event.Bucket.Name);
            context.Logger.Log("Key: " + s3Event.Object.Key);

            // Preform a check to make sure the filename matches the standard required.
            if (s3Event.Object.Key.EndsWith(DeploymentFileName))
            {
                context.Logger.Log("S3 Event corresponds to a properly formatted master.template CloudFormation document. Commencing deployment.");
            }
            else
            {
                context.Logger.Log("S3 Event does not match deployment requirements. Candidates for deployment must contain the primary CloudFormation template in a master.template file.");
                return("Impropper filename. No deployment processed");
            }

            // Display the commitinfo from the deployment
            string CommitInfoKeyName = s3Event.Object.Key.Replace(DeploymentFileName, CommitInfoFileName);

            context.Logger.Log($"Looking for accompanying commitinfo file: {CommitInfoKeyName}");
            try
            {
                CommitInfoResponse = await S3Client.GetObjectAsync(s3Event.Bucket.Name, CommitInfoKeyName);

                using (StreamReader reader = new StreamReader(CommitInfoResponse.ResponseStream))
                {
                    string contents = reader.ReadToEnd();
                    context.Logger.Log(contents);
                }
            }
            catch (Exception e)
            {
                context.Logger.Log(e.Message);
                context.Logger.Log("No commitinfo.txt file detected. Aborting Deployment");
                return("No accompanying commitinfo.txt. No deployment Processed");
            }

            // Get and set associated parameters
            string ParameterKeyName = s3Event.Object.Key.Replace(DeploymentFileName, ParameterFileName);

            context.Logger.Log($"Looking for accompanying parameter file: {ParameterKeyName}");
            try
            {
                ParameterFileResponse = await S3Client.GetObjectAsync(s3Event.Bucket.Name, ParameterKeyName);

                StreamReader reader    = new StreamReader(ParameterFileResponse.ResponseStream);
                string       paramline = reader.ReadLine();
                context.Logger.Log("Parameter file line being processed: " + paramline);
                while (!string.IsNullOrWhiteSpace(paramline))
                {
                    string[] paramstrings = paramline.Split(':');
                    if (paramstrings.Length == 2)
                    {
                        Parameters.Add(new Parameter()
                        {
                            ParameterKey   = paramstrings[0],
                            ParameterValue = paramstrings[1]
                        });
                    }
                    paramline = reader.ReadLine();
                    context.Logger.Log("Parameter file line being processed: " + paramline);
                }
            }
            catch (Exception e)
            {
                context.Logger.Log(e.Message);
                context.Logger.Log("No parameter file detected. Aborting Deployment.");
                return("No accompanying commitinfo.txt.No deployment Processed");
            }

            // The name of the stack will be based on the folder structure containing the master.template document.
            // As an example, a template deployed to the S3 key Knect/RCC/master.template would generate the stack Knect-RCC
            TargetStackName = s3Event.Object.Key.Replace("/", "-");
            TargetStackName = TargetStackName.Replace("-" + DeploymentFileName, "");
            context.Logger.Log("Cloudformation Stack Name: " + TargetStackName);

            // Gets a presigned url for the cloudformation client so it can access the master.template document.
            PresignedKeyRequest.BucketName = s3Event.Bucket.Name;
            PresignedKeyRequest.Key        = s3Event.Object.Key;
            PresignedKeyRequest.Expires    = DateTime.Now.AddMinutes(5);
            string PresignedS3Key = S3Client.GetPreSignedURL(PresignedKeyRequest);

            // If a stack with the target name already exists I want to update it. Otherwise I want to create a new stack.
            try
            {
                CurrentStacksRequest.StackName = TargetStackName;
                CurrentStacksResponse          = await CloudformationClient.DescribeStacksAsync(CurrentStacksRequest);

                context.Logger.Log("A stack for the target name already exists. The existing stack will be updated.");

                newstack = false;
            }
            catch
            {
                context.Logger.Log("No stack with the target name exists. A new stack will be created.");

                newstack = true;
            }

            foreach (Parameter param in Parameters)
            {
                context.Logger.Log($"Parameter is set Key: {param.ParameterKey} with value {param.ParameterValue}");
            }

            // If there is an existing stack I will update it. Otherwise I will create a new stack
            if (newstack == true)
            {
                // Create a new stack
                CreateStackRequest CreateStack = new CreateStackRequest();
                CreateStack.StackName   = TargetStackName;
                CreateStack.TemplateURL = PresignedS3Key;
                CreateStack.Parameters  = Parameters;
                CreateStack.Capabilities.Add("CAPABILITY_NAMED_IAM");

                await CloudformationClient.CreateStackAsync(CreateStack);

                return("A stack creation request was successfully generated");
            }
            else
            {
                UpdateStackRequest updatereq = new UpdateStackRequest();
                updatereq.StackName   = TargetStackName;
                updatereq.TemplateURL = PresignedS3Key;
                updatereq.Parameters  = Parameters;
                updatereq.Capabilities.Add("CAPABILITY_NAMED_IAM");

                await CloudformationClient.UpdateStackAsync(updatereq);

                return("A stack update request was successfully generated");
            }
        }
コード例 #15
0
ファイル: DeployStackFacade.cs プロジェクト: cythral/cfn
        public virtual async Task Deploy(DeployStackContext context)
        {
            var cloudformationClient = await cloudformationFactory.Create(context.RoleArn);

            var notificationArns = GetNotificationArns(context);
            var stackExists      = await DoesStackExist(context, cloudformationClient);

            var parameters   = (List <Parameter>)context.Parameters ?? new List <Parameter> {
            };
            var capabilities = (List <string>)context.Capabilities ?? new List <string> {
            };
            var tags         = (List <Tag>)context.Tags ?? new List <Tag> {
            };


            if (!stackExists)
            {
                var createStackRequest = new CreateStackRequest
                {
                    StackName          = context.StackName,
                    TemplateBody       = context.Template,
                    Parameters         = parameters,
                    Capabilities       = capabilities,
                    Tags               = tags,
                    NotificationARNs   = notificationArns,
                    RoleARN            = context.PassRoleArn,
                    ClientRequestToken = context.ClientRequestToken,
                    OnFailure          = DELETE
                };

                var createStackResponse = await cloudformationClient.CreateStackAsync(createStackRequest);

                logger.LogInformation($"Got create stack response: {Serialize(createStackResponse)}");
            }
            else
            {
                var updateStackRequest = new UpdateStackRequest
                {
                    StackName          = context.StackName,
                    TemplateBody       = context.Template,
                    Parameters         = parameters,
                    Capabilities       = capabilities,
                    Tags               = tags,
                    NotificationARNs   = notificationArns,
                    ClientRequestToken = context.ClientRequestToken,
                    RoleARN            = context.PassRoleArn
                };

                try
                {
                    var updateStackResponse = await cloudformationClient.UpdateStackAsync(updateStackRequest);

                    logger.LogInformation($"Got update stack response: {Serialize(updateStackResponse)}");
                }
                catch (Exception e)
                {
                    if (e.Message == "No updates are to be performed.")
                    {
                        throw new NoUpdatesException(e.Message);
                    }
                    else
                    {
                        throw e;
                    }
                }
            }
        }
コード例 #16
0
        private Task Commit(UpdateStackRequest r)
        {
            Func <Task> f = () => _cloudformation.UpdateStackAsync(r);

            return(Commit(f, r.StackName, StackStatus.UPDATE_COMPLETE));
        }