← Professional Cloud Architect: arquitetura e operação
19 / 25 · 120 MIN

Pipelines, APIs e evidência de aceitação

Interpretar resultados de entrega, limitar alterações e recolher dados sem transformar falhas parciais em sucesso.

Ligar cada critério de aceitação à evidência certa

Uma entrega tem vários resultados: o build pode terminar, a imagem pode ser publicada, o deployment pode instalar recursos e uma verificação pode testar o comportamento. Antes de aprovar, identifica qual destes resultados demonstra cada requisito. No caso fictício desta aula, uma aplicação de reporting exige um contrato funcional válido e uma verificação em pré-produção. O build tolerou a falha do contrato e ficou verde; a verificação posterior também falhou. A conclusão útil para o comité é que a aceitação continua pendente. Regista a execução, a entrega candidata, o critério em falta, o responsável e a próxima ação. Não uses a cor agregada como substituto destes factos. Em Cloud Deploy, repetir uma verificação falhada inicia outra execução e pode colocar o rollout em IN_PROGRESS. Esse estado ainda não demonstra aprovação nem rollback concluído. A equipa APS deve saber qual é o serviço atualmente exposto e que evidência falta antes de preparar a passagem a produção.

Ler a configuração de falhas, dependências e volumes

A revisão de um pipeline deve responder a três perguntas separadas: o que bloqueia, o que espera e o que persiste? Uma etapa com allowFailure pode falhar sem tornar o resultado global uma falha. Para um teste obrigatório, revê a configuração e executa um teste negativo controlado que demonstre o bloqueio. Depois desenha as dependências por ID. No excerto da pergunta, waitFor com o marcador de início permite ao publicador arrancar sem esperar pelo contrato, apesar de aparecer depois no YAML. Finalmente, distingue ordem e partilha de dados: esperar por uma etapa não copia o seu /tmp para outro contentor. Se o relatório precisa de atravessar etapas do mesmo build, usa /workspace ou um volume partilhado configurado. Confirma os caminhos de escrita e leitura. As imagens example.invalid usadas no exercício são identificadores fictícios para leitura; não constituem um pipeline executável. Na revisão entre equipas, pede uma demonstração concreta de cada uma das três propriedades.

Distinguir configuração, estado e objetos remotos

Uma revisão Terraform deve manter três perspetivas: a intenção escrita na configuração, a representação no estado e o que existe no fornecedor. Uma alteração manual autorizada pode criar diferenças entre elas. Um plano refresh-only propõe reconciliar estado e outputs com alterações externas; não é um pedido para repor o remoto no valor antigo e não reescreve os ficheiros HCL. O comando plan, isoladamente, não aplica a proposta. Depois de decidir como registar a intervenção, revê a configuração pretendida e um plano normal antes de avançar. O mesmo cuidado aparece no import clássico: associar uma base existente ao estado não demonstra que o bloco resource manual representa todos os atributos relevantes. Pede ao responsável uma revisão das diferenças e das alterações que o provider pretende realizar. Este exercício não executa Terraform nem substitui um ensaio num ambiente autorizado. A decisão de projeto deve distinguir import concluído de adoção operacional segura e reproduzível.

Identificar quem autentica cada interação

O sucesso de um comando local não prova que uma aplicação usa a mesma identidade. Num portátil, a sessão gcloud e a configuração ADC são distintas. Se a biblioteca não encontra credenciais, confirma primeiro o mecanismo configurado, antes de pedir permissões adicionais. Num contexto de desenvolvimento autorizado, prepara ADC pelo método apropriado e verifica a identidade sem expor tokens em logs. Numa entrega por Cloud Tasks, separa também a criação da tarefa da autenticação do pedido ao destino. No cenário, o destino recebe um ID token com audience diferente da esperada; dar mais permissões ao produtor na fila não corrige essa claim. Alinha a configuração com o contrato do serviço e observa uma nova entrega. O laboratório desta aula não usa credenciais nem invoca serviços. Ao preparar o handover, documenta identidade, finalidade, destino e evidência de cada fronteira, incluindo o comportamento quando a autenticação falha. Assim, operações consegue investigar a etapa correta sem alargar acesso por tentativa.

Exercício: terminar uma pesquisa apenas quando acaba a continuação

O inventário fictício começa com uma página vazia e um token de continuação. A segunda página contém report-A e outro token; a terceira contém report-B e termina a sequência. Antes de executar, escreve os três pedidos esperados e a lista final. Corre python3 content/labs/pca-pagination-contract/run.py e compara a previsão com o resultado. O coletor usa um callback local, mantém parent, filter e page_size, e transmite tokens sem interpretar o seu conteúdo. Continua mesmo quando items está vazio. Os limites positivos de páginas e de page_size são escolhas defensivas deste cliente didático; não são todas as regras de uma API AIP-158, que também admite page_size zero com valor por omissão do serviço. O exercício conserva valores repetidos em vez de os eliminar silenciosamente. Percorrer páginas não garante um snapshot de uma coleção em mudança. Para uma decisão de desativação, a equipa ainda precisa de definir consistência, âmbito temporal e confirmação das dependências reais.

Tornar falhas parciais visíveis ao consumidor

Um script de inventário pode recolher alguns recursos e falhar na página seguinte. Se devolver a lista parcial como sucesso, o relatório passa a confundir ausência com falha de recolha. No exercício, erros do callback, respostas inválidas, tokens repetidos e esgotamento do limite provocam falha explícita; não devolvem um resultado parcial aprovado. Podes inspecionar a implementação e alterar a sequência para repetir um token anterior. Explica por que motivo terminar esse ciclo com uma lista vazia seria perigoso para um comité de desativação. O modelo copia pedidos e resultados para que uma alteração num dicionário do callback não mude o âmbito do pedido seguinte. Não implementa retries, gestão de quotas, expiração de credenciais ou consistência distribuída. Num sistema real, define limites de tempo, tratamento de erros e observabilidade próprios. Uma alternativa de produto é apresentar recolha incompleta de forma explícita, impedindo que seja interpretada como confirmação de inventário vazio.

Limitar updates à intenção e testar rejeições

Quando uma API documenta FieldMask, o payload e a máscara têm papéis diferentes. No recurso fictício, selecionar enabled e enviar false desativa esse campo; enviar também label=B fora da máscara não altera label=A. Formula o pedido a partir da intenção, não de todos os campos que um formulário conhece. Uma máscara wildcard com semântica de substituição completa pode atingir campos mutáveis que um cliente antigo desconhece. O teste deve confirmar tanto a alteração pretendida como a preservação dos restantes valores. Aplica o mesmo raciocínio aos controlos de acesso: executar VerifyAPIKey não prova rejeição de uma chave inválida se continueOnError permite prosseguir e não existe outro bloqueio. Para cada controlo obrigatório, prepara um teste negativo com resultado esperado e ligação ao requisito. Os exemplos são contratos explicitamente definidos para o exercício; não assumas que qualquer API ou qualquer proxy tem essas configurações. Consulta a documentação e a configuração efetiva antes de generalizar.

Fechar a entrega com uma decisão que RUN consegue sustentar

Na simulação final, apresenta ao sponsor um registo curto: contrato falhado, configuração que tolera essa falha, verificação de pré-produção falhada e condições para nova avaliação. Propõe manter o serviço atual e replanear a janela enquanto os responsáveis investigam. Se o negócio pedir redução de âmbito, descreve exatamente o critério alterado, a autorização necessária e as limitações da nova decisão; não mantenhas a declaração de cumprimento total. Pede depois a um colega de operações que encontre a execução candidata, explique a dependência entre etapas, localize o relatório e distinga retry em curso de aceitação. Esse exercício testa a utilidade do handover. Como resumo, conserva cinco fronteiras: resultado agregado versus requisito, sequência versus persistência, estado versus configuração, autenticação do produtor versus destino e resposta parcial versus recolha concluída. Os tópicos relacionados são reconciliação de dados, observabilidade e governação de mudanças. A aprendizagem é conseguir justificar a decisão com evidência ligada ao requisito, sem prometer o que o ensaio não mediu.

"""Original local exercise. No network, credentials, SDK or snapshot guarantee."""
from copy import deepcopy
import json


def collect(fetch, parent, query_filter, page_size=20, max_pages=100):
    """Traverse a synthetic List contract; fail instead of returning partial success."""
    if not isinstance(parent, str) or not parent.strip():
        raise ValueError('parent must be nonempty text')
    if not isinstance(query_filter, str):
        raise ValueError('filter must be text')
    for value, label in [(page_size, 'page_size'), (max_pages, 'max_pages')]:
        if type(value) is not int or not 1 <= value <= 1000:
            raise ValueError(label + ' must be an integer from 1 to 1000')
    base = {'parent': parent, 'filter': query_filter, 'page_size': page_size}
    token, seen, items = '', set(), []
    for _ in range(max_pages):
        request = deepcopy(base)
        request['page_token'] = token
        response = fetch(request)
        if not isinstance(response, dict) or not isinstance(response.get('items'), list):
            raise ValueError('response must contain an items list')
        if len(response['items']) > page_size:
            raise ValueError('synthetic response exceeds requested page size')
        if any(not isinstance(item, dict) for item in response['items']):
            raise ValueError('synthetic items must be dictionaries')
        next_token = response.get('next_page_token', '')
        if not isinstance(next_token, str):
            raise ValueError('continuation token must be text')
        items.extend(deepcopy(response['items']))
        if not next_token:
            return items
        if next_token in seen:
            raise ValueError('repeated continuation token')
        seen.add(next_token)
        token = next_token
    raise ValueError('page bound reached before completion')


def scripted(pages):
    """Original in-memory fixture with exact opaque-token lookup."""
    calls = []
    def fetch(request):
        calls.append(deepcopy(request))
        return deepcopy(pages[request['page_token']])
    return fetch, calls


def main():
    names = []
    def check(name, condition):
        if not condition:
            raise AssertionError(name)
        names.append(name)
    def rejects(name, callback, kind=ValueError):
        try:
            callback()
        except kind:
            names.append(name)
        else:
            raise AssertionError(name)
    pages = {
        '': {'items': [], 'next_page_token': 'opaque_x7'},
        'opaque_x7': {'items': [{'id': 'report-A'}], 'next_page_token': 'opaque_Z2'},
        'opaque_Z2': {'items': [{'id': 'report-B'}]}
    }
    original = deepcopy(pages)
    fetch, calls = scripted(pages)
    result = collect(fetch, 'projects/fixture', 'state:READY')
    check('empty intermediate page is traversed', result == [{'id': 'report-A'}, {'id': 'report-B'}])
    check('opaque tokens are passed unchanged', [c['page_token'] for c in calls] == ['', 'opaque_x7', 'opaque_Z2'])
    check('parent remains stable', all(c['parent'] == 'projects/fixture' for c in calls))
    check('filter remains stable', all(c['filter'] == 'state:READY' for c in calls))
    check('page size remains stable by client choice', all(c['page_size'] == 20 for c in calls))
    check('input fixtures are not mutated', pages == original)
    check('missing continuation ends traversal', len(calls) == 3)
    f, c = scripted({'': {'items': [], 'next_page_token': ''}})
    check('empty final collection is accepted', collect(f, 'p', '') == [] and len(c) == 1)
    f, _ = scripted({'': {'items': [{'id': 'same'}], 'next_page_token': 'a'}, 'a': {'items': [{'id': 'same'}]}})
    check('repeated resource values are retained', collect(f, 'p', '') == [{'id': 'same'}, {'id': 'same'}])
    f, _ = scripted({'': {'items': [], 'next_page_token': 'a'}, 'a': {'items': [], 'next_page_token': 'a'}})
    rejects('self-repeating continuation fails', lambda: collect(f, 'p', ''))
    f, _ = scripted({'': {'items': [], 'next_page_token': 'a'}, 'a': {'items': [], 'next_page_token': 'b'}, 'b': {'items': [], 'next_page_token': 'a'}})
    rejects('multi-token cycle fails', lambda: collect(f, 'p', ''))
    f, c = scripted(pages)
    rejects('page budget does not return partial success', lambda: collect(f, 'p', '', max_pages=2))
    check('page budget limits callback invocations', len(c) == 2)
    f, _ = scripted(pages)
    check('completion on exact page bound succeeds', len(collect(f, 'p', '', max_pages=3)) == 2)
    for value, label in [(0, 'zero'), (-1, 'negative'), (True, 'boolean'), (2.5, 'fraction'), (1001, 'above limit')]:
        rejects('reject ' + label + ' page bound', lambda v=value: collect(lambda _: {}, 'p', '', max_pages=v))
    rejects('reject boolean page size', lambda: collect(lambda _: {}, 'p', '', page_size=True))
    rejects('reject zero page size in this client', lambda: collect(lambda _: {}, 'p', '', page_size=0))
    rejects('reject blank parent', lambda: collect(lambda _: {}, ' ', ''))
    rejects('reject nontext filter', lambda: collect(lambda _: {}, 'p', None))
    for response, label in [(None, 'nonobject response'), ({}, 'missing items'), ({'items': {}}, 'nonlist items'), ({'items': ['A']}, 'nonobject item'), ({'items': [], 'next_page_token': None}, 'nontext token')]:
        rejects('reject ' + label, lambda r=response: collect(lambda _: r, 'p', ''))
    rejects('reject oversized synthetic page', lambda: collect(lambda _: {'items': [{}, {}]}, 'p', '', page_size=1))
    def fails_second(request):
        if request['page_token']:
            raise RuntimeError('fixture transport failure')
        return {'items': [{'id': 'partial'}], 'next_page_token': 'later'}
    rejects('callback failure does not return partial data', lambda: collect(fails_second, 'p', ''), RuntimeError)
    mutation_calls = []
    def mutates_request(request):
        mutation_calls.append(deepcopy(request))
        token = request['page_token']
        request.update(parent='changed', filter='changed', page_size=1)
        return {'items': [], **({'next_page_token': 'a'} if not token else {})}
    collect(mutates_request, 'original', 'ready', page_size=20)
    check('callback mutation cannot change next request scope', mutation_calls[1] == {'parent': 'original', 'filter': 'ready', 'page_size': 20, 'page_token': 'a'})
    external = {'items': [{'nested': {'value': 1}}]}
    output = collect(lambda _: external, 'p', '')
    output[0]['nested']['value'] = 9
    check('output nested objects are isolated', external['items'][0]['nested']['value'] == 1)
    print(json.dumps({'passed': len(names), 'checks': names, 'result': result, 'requests': calls, 'network': False, 'vendorExecution': False, 'persistentWrites': False}))


if __name__ == '__main__':
    main()
NA PRÁTICA

Um build verde tolerou um teste obrigatório falhado; uma verificação posterior também falhou. A equipa precisa de nova evidência antes de aprovar.

Armadilhas comuns

Confundir cor verde com aceitação, ordem com partilha de ficheiros, refresh com reversão, login CLI com ADC e página vazia com fim da pesquisa.

Tópicos relacionados: Reconciliação e inventários completos · Governação de mudanças e passagem a RUN · Identidades de execução e observabilidade

Leva esta ideia contigo

Liga cada conclusão à evidência que a suporta e testa explicitamente as falhas que devem impedir a aprovação.

Criar conta

Referência: Build configuration file schema · Current linked standard guide; edition date unconfirmed (2026-09-30 inspection)

Google Cloud é uma marca comercial de Google LLC. A dr.pt é uma plataforma de preparação independente e não está afiliada, associada, patrocinada, autorizada nem aprovada por Google. Os conteúdos e as perguntas são originais, não são perguntas oficiais de exame, e concluir os nossos testes não atribui nem garante qualquer certificação. Os nomes são usados apenas para identificar o tema. Todas as outras marcas pertencem aos respetivos titulares.