将 API Gateway Cloudformation 模板转换为 Swagger 文件

Convert API Gateway Cloudformation template to Swagger file

Coludformation 模板中描述了现有的 API。现在我想使用 Swagger 记录 API。有没有办法解析 Cloudformation 模板以创建 swagger.yaml 规范文件?如果可能的话,我想避免第二次写 API。

注意:我知道您可以使用 Swagger 定义 API,然后在 Cloudformation 模板中导入 API 配置。这不是我需要的。 Cloudformation 已经存在,不会更改。因此,我需要相反的东西:基于现有 Cloudformation 模板的 Swagger 配置文件。

无法将模板转换为我所知道的 swagger 文件。但是,如果您正在寻找一种方法将服务规范仅保存在一个地方(模板)并且您已经部署了它,您可以从舞台上获取 swagger 或 OAS 文件(因此您也必须有一个舞台)至少两种方式:

  1. 通过 Web 控制台。使用亚马逊 API 网关-> APIs->您的 API->舞台>您的舞台 -> 导出选项卡。见图:exporting Swagger or OAS as a file by Web console

  2. aws apigateway get-export ... 下面是一个例子:

aws apigateway get-export --rest-api-id ${API_ID} --stage-name ${STAGE_NAME} --export-type swagger swagger.json

我刚刚做了这个,它的设置并不完美 plug/play,但会让你知道你需要调整什么才能让它工作(还需要确保你的 CF 模板已经设置好,所以它有需要的信息,在我的身上我不得不添加一些我丢失的 requestParams,也使用这个网站来测试你从这个代码得到的结果,看看它是否适用于 swagger):

const yaml = require('js-yaml');
const fs = require('fs');

// Get document, or throw exception on error
try {
  // loads file from local
  const inputStr = fs.readFileSync('../template.yaml', { encoding: 'UTF-8' });
  // creating a schema to handle custom tags (cloud formation) which then js-yaml can handle when parsing
  const CF_SCHEMA = yaml.DEFAULT_SCHEMA.extend([
    new yaml.Type('!ImportValue', {
      kind: 'scalar',
      construct: function (data) {
        return { 'Fn::ImportValue': data };
      },
    }),
    new yaml.Type('!Ref', {
      kind: 'scalar',
      construct: function (data) {
        return { Ref: data };
      },
    }),
    new yaml.Type('!Equals', {
      kind: 'sequence',
      construct: function (data) {
        return { 'Fn::Equals': data };
      },
    }),
    new yaml.Type('!Not', {
      kind: 'sequence',
      construct: function (data) {
        return { 'Fn::Not': data };
      },
    }),
    new yaml.Type('!Sub', {
      kind: 'scalar',
      construct: function (data) {
        return { 'Fn::Sub': data };
      },
    }),
    new yaml.Type('!If', {
      kind: 'sequence',
      construct: function (data) {
        return { 'Fn::If': data };
      },
    }),
    new yaml.Type('!Join', {
      kind: 'sequence',
      construct: function (data) {
        return { 'Fn::Join': data };
      },
    }),
    new yaml.Type('!Select', {
      kind: 'sequence',
      construct: function (data) {
        return { 'Fn::Select': data };
      },
    }),
    new yaml.Type('!FindInMap', {
      kind: 'sequence',
      construct: function (data) {
        return { 'Fn::FindInMap': data };
      },
    }),
    new yaml.Type('!GetAtt', {
      kind: 'scalar',
      construct: function (data) {
        return { 'Fn::GetAtt': data };
      },
    }),
    new yaml.Type('!GetAZs', {
      kind: 'scalar',
      construct: function (data) {
        return { 'Fn::GetAZs': data };
      },
    }),
    new yaml.Type('!Base64', {
      kind: 'mapping',
      construct: function (data) {
        return { 'Fn::Base64': data };
      },
    }),
  ]);
  const input = yaml.load(inputStr, { schema: CF_SCHEMA });
  // now that we have our AWS yaml copied and formatted into an object, lets pluck what we need to match up with the swagger.yaml format
  const rawResources = input.Resources;
  let guts = [];
  // if an object does not contain a properties.path object then we need to remove it as a possible api to map for swagger
  for (let i in rawResources) {
    if (rawResources[i].Properties.Events) {
      for (let key in rawResources[i].Properties.Events) {
        // console.log(i, rawResources[i]);
        if (rawResources[i].Properties.Events[key].Properties.Path) {
          let tempResource = rawResources[i].Properties.Events[key].Properties;
          tempResource.Name = key;
          guts.push(tempResource);
        }
      }
    }
  } // console.log(guts);
  const defaultResponses = {
    '200': {
      description: 'successful operation',
    },
    '400': {
      description: 'Invalid ID supplied',
    },
  };
  const formattedGuts = guts.map(function (x) {
    if (x.RequestParameters) {
      if (
        Object.keys(x.RequestParameters[0])[0].includes('path') &&
        x.RequestParameters.length > 1
      ) {
        return {
          [x.Path]: {
            [x.Method]: {
              tags: [x.RestApiId.Ref],
              summary: x.Name,
              parameters: [
                {
                  name: Object.keys(x.RequestParameters[0])[0].split('method.request.path.')[1],
                  in: 'path',
                  type: 'string',
                  required: Object.values(x.RequestParameters[0])[0].Required,
                },
                {
                  name: Object.keys(x.RequestParameters[1])[0].split('method.request.path.')[1],
                  in: 'path',
                  type: 'string',
                  required: Object.values(x.RequestParameters[1])[0].Required,
                },
              ],
              responses: defaultResponses,
            },
          },
        };
      } else if (Object.keys(x.RequestParameters[0])[0].includes('path')) {
        return {
          [x.Path]: {
            [x.Method]: {
              tags: [x.RestApiId.Ref],
              summary: x.Name,
              parameters: [
                {
                  name: Object.keys(x.RequestParameters[0])[0].split('method.request.path.')[1],
                  in: 'path',
                  type: 'string',
                  required: Object.values(x.RequestParameters[0])[0].Required,
                },
              ],
              responses: defaultResponses,
            },
          },
        };
      } else if (Object.keys(x.RequestParameters[0])[0].includes('querystring')) {
        return {
          [x.Path]: {
            [x.Method]: {
              tags: [x.RestApiId.Ref],
              summary: x.Name,
              parameters: [
                {
                  name: Object.keys(x.RequestParameters[0])[0].split(
                    'method.request.querystring.'
                  )[1],
                  in: 'query',
                  type: 'string',
                  required: Object.values(x.RequestParameters[0])[0].Required,
                },
              ],
              responses: defaultResponses,
            },
          },
        };
      }
    }
    return {
      [x.Path]: {
        [x.Method]: {
          tags: [x.RestApiId.Ref],
          summary: x.Name,
          responses: defaultResponses,
        },
      },
    };
  });
  const swaggerYaml = yaml.dump(
    {
      swagger: '2.0',
      info: {
        description: '',
        version: '1.0.0',
        title: '',
      },
      paths: Object.assign({}, ...formattedGuts),
    },
    { noRefs: true }
  ); // need to keep noRefs as true, otherwise you will see "*ref_0" instead of the response obj
  //   console.log(swaggerYaml);
  fs.writeFile('../swagger.yaml', swaggerYaml, 'utf8', function (err) {
    if (err) return console.log(err);
  });
} catch (e) {
  console.log(e);
  console.log('error above');
}