Aceder aos metadados de instâncias

A Google Cloud Platform fornece um servidor de metadados que conhece os detalhes da sua instância do App Engine, como o ID do projeto que a contém, as contas de serviço e os tokens usados pelas contas de serviço. Pode aceder a estes dados através de pedidos HTTP simples: não são necessárias bibliotecas cliente.

Esta página mostra como aceder aos metadados da instância a partir da sua aplicação de tempo de execução Java 8 implementada fazendo chamadas HTTP aos pontos finais do servidor de metadados adequados.

Uma forma útil de usar esta API é obter o token da conta de serviço e fornecê-lo como um token de portador no cabeçalho de autorização de uma das APIs Google Cloud, para autenticar a sua aplicação nesse serviço de API específico. Consulte a documentação da API Google Cloud Translation para ver um exemplo de como estes tokens de autorização são usados.

Identificar que ponto final de metadados usar

A tabela seguinte lista os pontos finais onde pode fazer pedidos HTTP para metadados específicos. O servidor de metadados está acessível em http://metadata.google.internal.

Ponto final de metadados Descrição
/computeMetadata/v1/project/numeric-project-id O número do projeto atribuído ao seu projeto.
/computeMetadata/v1/project/project-id O ID do projeto atribuído ao seu projeto.
/computeMetadata/v1/instance/zone A zona na qual a instância está a ser executada.
/computeMetadata/v1/instance/service-accounts/default/aliases
/computeMetadata/v1/instance/service-accounts/default/email O email da conta de serviço predefinido atribuído ao seu projeto.
/computeMetadata/v1/instance/service-accounts/default/ Apresenta todas as contas de serviço predefinidas do seu projeto.
/computeMetadata/v1/instance/service-accounts/default/scopes Lista todos os âmbitos suportados para as contas de serviço predefinidas.
/computeMetadata/v1/instance/service-accounts/default/token Devolve o token de autorização que pode ser usado para autenticar a sua aplicação noutras APIs Google Cloud.

Por exemplo, para obter o ID do projeto, envie um pedido para http://metadata.google.internal/computeMetadata/v1/project/project-id.

Fazer pedidos de metadados

O seguinte exemplo de código obtém todos os metadados disponíveis para a instância e apresenta-os, exceto o token da conta de serviço.

@SuppressWarnings("serial")
// With @WebServlet annotation the webapp/WEB-INF/web.xml is no longer required.
@WebServlet(name = "Metadata", description = "Metadata: Write info about GAE Standard",
    urlPatterns = "/metadata")
public class MetadataServlet extends HttpServlet {

  private final String[] metaPath = {
      "/computeMetadata/v1/project/numeric-project-id", //  (pending)
      "/computeMetadata/v1/project/project-id",
      "/computeMetadata/v1/instance/zone",
      "/computeMetadata/v1/instance/service-accounts/default/aliases",
      "/computeMetadata/v1/instance/service-accounts/default/email",
      "/computeMetadata/v1/instance/service-accounts/default/",
      "/computeMetadata/v1/instance/service-accounts/default/scopes",
      // Tokens work - but are a security risk to display
      //      "/computeMetadata/v1/instance/service-accounts/default/token"
  };

  final String[] metaServiceAcct = {
      "/computeMetadata/v1/instance/service-accounts/{account}/aliases",
      "/computeMetadata/v1/instance/service-accounts/{account}/email",
      "/computeMetadata/v1/instance/service-accounts/{account}/scopes",
      // Tokens work - but are a security risk to display
      //     "/computeMetadata/v1/instance/service-accounts/{account}/token"
  };

  private final String metadata = "http://metadata.google.internal";
  private TemplateEngine templateEngine;

  // Use OkHttp from Square as it's quite easy to use for simple fetches.
  private final OkHttpClient ok = new OkHttpClient.Builder()
      .readTimeout(500, TimeUnit.MILLISECONDS)  // Don't dawdle
      .writeTimeout(500, TimeUnit.MILLISECONDS)
      .build();

  // Setup to pretty print returned json
  private final Gson gson = new GsonBuilder()
      .setPrettyPrinting()
      .create();
  private final JsonParser jp = new JsonParser();

  // Fetch Metadata
  String fetchMetadata(String key) throws IOException {
    Request request = new Request.Builder()
        .url(metadata + key)
        .addHeader("Metadata-Flavor", "Google")
        .get()
        .build();

    Response response = ok.newCall(request).execute();
    return response.body().string();
  }

  String fetchJsonMetadata(String prefix) throws IOException {
    Request request = new Request.Builder()
        .url(metadata + prefix)
        .addHeader("Metadata-Flavor", "Google")
        .get()
        .build();

    Response response = ok.newCall(request).execute();

    // Convert json to prety json
    return gson.toJson(jp.parse(response.body().string()));
  }

  @Override
  public void init() {
    // Setup ThymeLeaf
    ServletContextTemplateResolver templateResolver =
        new ServletContextTemplateResolver(this.getServletContext());

    templateResolver.setPrefix("/WEB-INF/templates/");
    templateResolver.setSuffix(".html");
    templateResolver.setCacheTTLMs(Long.valueOf(1200000L)); // TTL=20m

    // Cache is set to true by default. Set to false if you want templates to
    // be automatically updated when modified.
    templateResolver.setCacheable(true);

    templateEngine = new TemplateEngine();
    templateEngine.setTemplateResolver(templateResolver);
  }

  @Override
  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    String defaultServiceAccount = "";
    WebContext ctx = new WebContext(req, resp, getServletContext(), req.getLocale());

    resp.setContentType("text/html");

    String environment =
        (String) System.getProperties().get("com.google.appengine.runtime.environment");
    ctx.setVariable("production", environment);

    // The metadata server is only on a production system
    if (environment.equals("Production")) {

      TreeMap<String, String> m = new TreeMap<>();

      for (String key : metaPath) {
        m.put(key, fetchMetadata(key));
        if (key.contains("default/email")) {
          defaultServiceAccount = m.get(key);
        }
      }

      ctx.setVariable("Metadata", m.descendingMap());

      m = new TreeMap<>();
      for (String key : metaServiceAcct) {
        // substitute a service account for {account}
        key = key.replace("{account}", defaultServiceAccount);
        m.put(key, fetchMetadata(key));
      }
      ctx.setVariable("sam", m.descendingMap());

      // Recursivly get all info about service accounts -- Note tokens are leftout by default.
      ctx.setVariable("rsa",
          fetchJsonMetadata("/computeMetadata/v1/instance/service-accounts/?recursive=true"));
      // Recursivly get all data on Metadata server.
      ctx.setVariable("ram", fetchJsonMetadata("/?recursive=true"));
    }

    templateEngine.process("index", ctx, resp.getWriter());

  }
}

No exemplo de código, repare na verificação para garantir que a app está a ser executada em produção. Se a app estiver a ser executada localmente, não são devolvidos metadados dos pedidos.

Tenha também em atenção a utilização do serializador / desserializador Google Gson JSON, do cliente HTTP e HTTP2 OkHttp e do sistema de modelos Thymeleaf. Estas não são obrigatórias, mas são bibliotecas úteis para os seus próprios projetos.

Execução local

O servidor de metadados está disponível para aplicações implementadas: a execução local no servidor de desenvolvimento não é suportada. Pode adicionar uma verificação do ambiente ao seu código para esperar resultados de metadados apenas se a app estiver a ser executada em produção, conforme mostrado no exemplo de código fornecido acima:

String environment =
      (String) System.getProperties().get("com.google.appengine.runtime.environment");
  ctx.setVariable("production", environment);

  // The metadata server is only on a production system
  if (environment.equals("Production")) {
     ... //show metadata results
   }