Как динамически создавать корзины s3 в конвейере azure devops CI/CD

#amazon-web-services #amazon-s3 #azure-devops #yaml #amazon-cloudformation

Вопрос:

Я хочу автоматизировать процесс создания корзины с помощью конвейера CI/CD на основе данных, упомянутых в одном из файлов yaml. Итак, у меня есть ведро.файл yaml, содержащий имена всех сегментов. Этот файл продолжает меняться по мере добавления новых имен блоков в будущем. В настоящее время именно так обстоит дело с ведром.ямл выглядит

 BucketName:
    - test-bucket
    - test-bucket2
    - test-bucket3
 

У меня есть один шаблон.файл yaml, который является шаблоном cloudformation для создания корзин s3. Вот как это выглядит:

 Resources:
  S3Bucket:
    Type: 'AWS::S3::Bucket'
    DeletionPolicy: Retain
    Properties:
      BucketName: This will come from bucket.yaml
 

Итак, шаблон.yaml будет извлекать имена ведер из ведра.файл yaml и должен создать 3 ведра, как указано в файле bucket.yaml. Если кто-то добавит еще 2 ведра в ведро.ямл, затем шаблон.yaml также должен создать эти 2 новых ведра. Кроме того, если кто-то удалит какое-либо имя корзины из корзины.yaml, тогда эти ведра также должны быть удалены. Я не мог выяснить процесс в своих исследованиях, просто нашел информацию по кусочкам.Итак, здесь у меня есть конкретные вопросы, если это возможно сделать:

  1. Как получить имена ведер из ведра.yaml и шаблон.yaml должен создать все ведра.
  2. Если кто-то обновит/добавит/удалит имя корзины в корзине.ямл, шаблон.yaml должен соответствующим образом обновить их. Кроме того, пожалуйста, объясните, как я буду это делать с помощью конвейера CI/CD в Azure DevOps.

Ответ №1:

  1. По поводу вашего первого вопроса:

Как получить имена ведер из ведра.yaml и шаблон.yaml должен создать все ведра.

В bucket.yaml вы можете использовать параметры для настройки BucketName .

Например:

 parameters:
- name: BucketName
  type: object
  default:
  - test-bucket
  - test-bucket2
  - test-bucket3

steps:
- ${{ each value in parameters.BucketName }}:
  - script: echo ${{ value }}
 

На этом шаге можно просмотреть значения параметра BucketName .

В template.yaml том, что вы можете позвонить bucket.yaml , как показано ниже.

 trigger:
- main

extends:
  template: bucket.yaml
 
  1. По вашему второму вопросу:

Если кто-то обновит/добавит/удалит имя корзины в корзине.ямл, шаблон.yaml должен соответствующим образом обновить их.

Нет никакого простого способа сделать это. Вы можете попробовать написать сценарий для запуска в конвейере, чтобы выполнить следующие действия:

  • Перечислите все созданные сегменты. Это список существующих корзин.
  • Сравните список существующих сегментов со списком значений параметра BucketName , чтобы проверить, какие сегменты необходимо добавить, а какие необходимо удалить.
  • Если в параметре указано ведро, но его нет в существующих ведрах, это ведро должно быть создано как новое ведро.
  • Если ведро указано в существующих ведрах, но не в параметре, это ведро следует удалить.

Комментарии:

1. Могу ли я использовать эти шаблоны yaml в своей задаче создания/обновления CF в конвейерах CI/CD?

2. Привет @RamanBalyan, обычно вы не можете использовать шаблон в задаче конвейера. Шаблон используется для повторного использования этапов, заданий, шагов и переменных в конвейере YAML. Но вызов задачи в другой задаче не поддерживается.

Ответ №2:

 BucketName:
                  - test-bucket
                  - test-bucket2
                  - test-bucket3
 

Требования подразумевают, что все сегменты S3 будут созданы одинаковым образом и что никаких отклонений от заданного шаблона Cloudformation ( AWS::S3::Bucket ) не требуется.

Требования требуют, чтобы мы отслеживали, какие сегменты S3 необходимо удалить. Cloudformation не удалит сегменты S3, так как фрагмент шаблона Cloudformation содержит политику удаления сохранения.

Решение:

Сегменты S3 могут быть помечены определенным образом, чтобы идентифицировать их как принадлежащие текущему конвейеру CI/CD. Сегменты S3 могут быть перечислены, и все сегменты S3, которые помечены правильным образом, но все же не существуют, bucket.yaml могут быть удалены.


Я бы лично просто создал корзины S3, необходимые для конвейера CI/CD, с помощью AWS SDK, и вручную управлял удалением корзины S3. Если приложению требуется корзина S3, они должны создать ее самостоятельно в стеке Cloudformation своего приложения, чтобы они могли !Изменить ее и настроить так, как они хотят (например, шифрование в состоянии покоя, управление версиями, правила жизненного цикла и т. Д.).


Техническое примечание:

Для удаления корзины S3 также необходимо удалить ее содержимое. Для этого нам потребуется перечислить все объекты в корзине S3, а затем удалить их. Некоторая документация для Java SDK [здесь]. Только впоследствии вызов API для удаления корзины S3 завершится успешно.

Вы можете заставить Cloudformation удалить ваши объекты S3 с помощью пользовательского ресурса. Тем не менее, я не нахожу пользовательские ресурсы, с которыми интересно работать, поэтому, если вы можете использовать AWS SDK в своем конвейере CI/CD, я бы, вероятно, просто использовал это.

Пользовательский ресурс для удаления содержимого корзины может выглядеть примерно так в Cloudformation: (Это пользовательский ресурс, который использует Лямбду. Лямбда-код удалит содержимое корзины S3, если пользовательский ресурс будет удален)

  # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cfn-customresource.html
  # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/walkthrough-custom-resources-lambda-lookup-amiids.html
  ExampleBucketOperationCustomResource:
    Type: AWS::CloudFormation::CustomResource
    DependsOn: [Bucket, ExampleBucketOperationLambdaFunction]
    Properties:
      ServiceToken: !GetAtt ExampleBucketOperationLambdaFunction.Arn
      # Custom properties
      BucketToUse: !Ref S3BucketName
  ExampleBucketOperationLambdaFunctionExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: "ExampleBucketOperationLambda-ExecutionRole"
      Path: "/"
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action:
              - sts:AssumeRole
            Principal:
              Service:
                - lambda.amazonaws.com
      Policies:
        - PolicyName: "ExampleBucketOperationLambda-CanAccessCloudwatchLogs"
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: arn:aws:logs:*:*:*
        - PolicyName: "ExampleBucketOperationLambda-S3BucketLevelPermissions"
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - s3:ListBucket
                Resource:
                  - !Sub "arn:aws:s3:::${S3BucketName}"
        - PolicyName: "ExampleBucketOperationLambda-S3ObjectLevelPermissions"
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - s3:DeleteObject
                  - s3:PutObject
                Resource:
                  - !Sub "arn:aws:s3:::${S3BucketName}/*"
  # Test payload:
  # {"RequestType":"Create","ResourceProperties":{"BucketToUse":"your-bucket-name"}}
  ExampleBucketOperationLambdaFunction:
    Type: AWS::Lambda::Function
    DependsOn: ExampleBucketOperationLambdaFunctionExecutionRole
    # DeletionPolicy: Retain
    Properties:
      FunctionName: "ExampleBucketOperationLambda"
      Role: !GetAtt ExampleBucketOperationLambdaFunctionExecutionRole.Arn
      Runtime: python3.8
      Handler: index.handler
      Timeout: 30
      Code:
        ZipFile: |
          import boto3
          import cfnresponse
          def handler(event, context):
              eventType = event["RequestType"]
              print("The event type is: "   str(eventType));
              bucketToUse = event["ResourceProperties"]["BucketToUse"]
              print("The bucket to use: "   str(bucketToUse));
              try:
                  # Requires s3:ListBucket permission
                  if (eventType in ["Delete"]):
                      print("Deleting everyting in bucket: "   str(bucketToUse));
                      s3Client = boto3.client("s3")
                      s3Bucket = boto3.resource("s3").Bucket(bucketToUse)
                      for currFile in s3Bucket.objects.all():
                          print("Deleting file: "   currFile.key);
                          s3Client.delete_object(Bucket=bucketToUse, Key=currFile.key)
                  
                  print("All done")
                  responseData = {}
                  cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData)
              except Exception as e:
                  responseData = {}
                  errorDetail = "Exception: "   str(e)
                  errorDetail = errorDetail   "nt More detail can be found in CloudWatch Log Stream: "   context.log_stream_name
                  print(errorDetail)
                  cfnresponse.send(event=event, context=context, responseStatus=cfnresponse.FAILED, responseData=responseData, reason=errorDetail)
 

Ответ №3:

Спасибо за приведенные выше ответы. Я выбрал другой путь для решения этой проблемы. Я использовал AWS CDK, чтобы реализовать то, что я точно хотел. Я лично использовал AWS CDK для Python и создал инфраструктуру с его помощью.