https://partner.cloudskillsboost.google/focuses/32743?parent=catalog
My solution step-by-step and source files.
In the GCP Console for the Apigee project, open the Cloud Shell and enter the following commands:
# Enable prerequisite APIs
gcloud services enable compute.googleapis.com
gcloud services enable servicenetworking.googleapis.com
gcloud services enable apigee.googleapis.com
# Enable Cloud Translation API
gcloud services enable translate.googleapis.com
# Enable IAM API
gcloud services enable iam.googleapis.com
# Create service account `apigee-proxy` with role: Logging > Logs Writer
gcloud iam service-accounts create apigee-proxy --display-name="apigee-proxy"
gcloud projects add-iam-policy-binding ${GOOGLE_CLOUD_PROJECT} --member="serviceAccount:apigee-proxy@${GOOGLE_CLOUD_PROJECT}.iam.gserviceaccount.com" --role="roles/logging.logWriter"
echo "Service Account Name for Apigee deployment: apigee-proxy@${GOOGLE_CLOUD_PROJECT}.iam.gserviceaccount.com"
If you are running this in your own Apigee lab (not in Cloud Skills Boost), you'll need to enable an Apigee Evaluation first using the Apigee UI. Follow the wizard and assume defaults were appropriate. For the last question in the wizard, do NOT create a Load Balancer (to save costs for testing) - your API will then only be accessible locally, via a test VM.
In the Apigee UI, go to Admin > Environments > Groups, on the eval-group
click on the Edit icon (pencil). In the Hostnames field, add a new line: eval.example.com
and click Save. This allows us to replicate the same behaviour as the Challenge Lab by using the same hostname without having to override the Host header or additional parameters.
Create that VM using the following commands in Cloud Shell:
export PROJECT_ID=$GOOGLE_CLOUD_PROJECT
export AUTH="Authorization: Bearer $(gcloud auth print-access-token)"
export SUBNET=default
export VM_INSTANCE_NAME=apigeex-test-vm
export PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format="value(projectNumber)")
export VM_REGION=$(curl -s -H "$AUTH" https://apigee.googleapis.com/v1/organizations/$PROJECT_ID/instances | jq -r '.instances[0].location')
export VM_ZONE=$(gcloud compute zones list | grep ${VM_REGION} | head -n 1 | awk '{print $2}')
# Create VM
gcloud compute --project=$PROJECT_ID \
instances create $VM_INSTANCE_NAME \
--zone=$VM_ZONE \
--machine-type=e2-micro \
--subnet=$SUBNET \
--network-tier=PREMIUM \
--no-restart-on-failure \
--maintenance-policy=TERMINATE \
--preemptible \
--service-account=$PROJECT_NUMBER-compute@developer.gserviceaccount.com \
--scopes=https://www.googleapis.com/auth/cloud-platform \
--tags=http-server,https-server \
--image=debian-10-buster-v20210122 \
--image-project=debian-cloud \
--boot-disk-size=10GB \
--boot-disk-type=pd-standard \
--boot-disk-device-name=$VM_INSTANCE_NAME \
--no-shielded-secure-boot \
--shielded-vtpm \
--shielded-integrity-monitoring \
--reservation-affinity=any
# Connect to VM
gcloud compute ssh $VM_INSTANCE_NAME --zone=$VM_ZONE --project=$PROJECT_ID
### On the VM run the following commands
sudo apt-get update -y
sudo apt-get install -y jq
cat <<EOF >> ~/.bashrc
export AUTH="Authorization: Bearer \$(gcloud auth print-access-token)"
export PROJECT_ID=\$(curl -s -H "Metadata-Flavor: Google" http://metadata.google.internal/computeMetadata/v1/project/project-id)
export ENV_GROUP_HOSTNAME=\$(curl -H "\$AUTH" https://apigee.googleapis.com/v1/organizations/\$PROJECT_ID/envgroups -s | jq -r '.environmentGroups[0].hostnames[0]')
export INTERNAL_LOAD_BALANCER_IP=\$(curl -H "\$AUTH" https://apigee.googleapis.com/v1/organizations/\$PROJECT_ID/instances -s | jq -r '.instances[0].host')
export APIGEE_API_HOST=example.\$PROJECT_ID.apigee.internal
EOF
source ~/.bashrc
sudo -- sh -c "echo $INTERNAL_LOAD_BALANCER_IP $APIGEE_API_HOST eval.example.com >> /etc/hosts"
curl -H "$AUTH" https://apigee.googleapis.com/v1/organizations/$PROJECT_ID | jq -r .caCertificate | base64 -d > cacert.crt
sudo cp cacert.crt /usr/local/share/ca-certificates/apigee-cacert.crt
sudo update-ca-certificates
You can test that this works on the VM by issuing the following calls:
# Send a test request to the hello-world API to verify connectivity
curl -is -H "Host: $ENV_GROUP_HOSTNAME" \
https://$APIGEE_API_HOST/hello-world \
--cacert cacert.crt \
--resolve example.$PROJECT_ID.apigee.internal:443:$INTERNAL_LOAD_BALANCER_IP
# Test using eval.example.com
curl -i -k "https://eval.example.com/hello-world"
-
In a new tab, navigate to the Apigee UI: https://apigee.google.com
-
Create a Reverse Proxy named
translate-v1
with a base path of/translate/v1
and target URL:https://translation.googleapis.com/language/translate/v2
. Click Next, and then Next again - do not make any other changes. Click Create. -
Click Edit Proxy and then click the Develop tab.
-
Under Target Endpoints, click
default
to edit it. Copy/paste the contents from targets/default.xml which adds the following section underHTTPTargetConnection
for Authentication using a GoogleAccessToken:<Authentication> <GoogleAccessToken> <Scopes> <Scope>https://www.googleapis.com/auth/cloud-translation</Scope> </Scopes> </GoogleAccessToken> </Authentication>
-
Click Save.
-
Click Deploy to eval and for Service Account use the one created earlier (outputted in the Cloud Shell)
-
Run the following in Cloud Shell and do not proceed until you see
ORG IS READY TO USE
export INSTANCE_NAME=eval-instance; export ENV_NAME=eval; export PREV_INSTANCE_STATE=; echo "waiting for runtime instance ${INSTANCE_NAME} to be active"; while : ; do export INSTANCE_STATE=$(curl -s -H "Authorization: Bearer $(gcloud auth print-access-token)" -X GET "https://apigee.googleapis.com/v1/organizations/${GOOGLE_CLOUD_PROJECT}/instances/${INSTANCE_NAME}" | jq "select(.state != null) | .state" --raw-output); [[ "${INSTANCE_STATE}" == "${PREV_INSTANCE_STATE}" ]] || (echo; echo "INSTANCE_STATE=${INSTANCE_STATE}"); export PREV_INSTANCE_STATE=${INSTANCE_STATE}; [[ "${INSTANCE_STATE}" != "ACTIVE" ]] || break; echo -n "."; sleep 5; done; echo; echo "instance created, waiting for environment ${ENV_NAME} to be attached to instance"; while : ; do export ATTACHMENT_DONE=$(curl -s -H "Authorization: Bearer $(gcloud auth print-access-token)" -X GET "https://apigee.googleapis.com/v1/organizations/${GOOGLE_CLOUD_PROJECT}/instances/${INSTANCE_NAME}/attachments" | jq "select(.attachments != null) | .attachments[] | select(.environment == \"${ENV_NAME}\") | .environment" --join-output); [[ "${ATTACHMENT_DONE}" != "${ENV_NAME}" ]] || break; echo -n "."; sleep 5; done; echo "***ORG IS READY TO USE***";
-
Test the API proxy.
-
In Cloud Shell, open an SSH connection to the
apigeex-test-vm
export PROJECT_ID=$GOOGLE_CLOUD_PROJECT export AUTH="Authorization: Bearer $(gcloud auth print-access-token)" export VM_INSTANCE_NAME=apigeex-test-vm export PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format="value(projectNumber)") export VM_REGION=$(curl -s -H "$AUTH" https://apigee.googleapis.com/v1/organizations/$PROJECT_ID/instances | jq -r '.instances[0].location') export VM_ZONE=$(gcloud compute zones list | grep ${VM_REGION} | head -n 1 | awk '{print $2}') gcloud compute ssh $VM_INSTANCE_NAME --zone=$VM_ZONE --force-key-file-overwrite
NOTE: Check the value of
$VM_ZONE
- it may not match the lab's expected zone, so you may have to override it if the SSH command above does not connect. As an example,export VM_ZONE=us-west1-a
and then run thegcloud compute ssh
command above to connect. -
On the test VM, run the following command:
curl -i -k -X POST "https://eval.example.com/translate/v1" \ -H "Content-Type: application/json" \ -d '{ "q": "Translate this text!", "target": "es" }'
-
Observe the response. Note the request and response are being proxied through as-is. It should look similar to the following:
{ "data": { "translations": [ { "translatedText": "¡Traduce este texto!", "detectedSourceLanguage": "en" } ] } }
-
- Click the Check my progress button to complete the task.
Using the files in this repo, perform the following steps in order using the Apigee UI:
- Add the Resources using contents from the referenced files:
- JavaScript File - BuildLanguagesResponse.js
- Property Set - language.properties
- Add Policies using contents from the referenced files:
- Extension > JavaScript: JS-BuildLanguagesResponse -> Script File: BuildLanguagesResponse.js
- Mediation > Assign Message:
- Add Proxy Endpoints - see proxies/default.xml
Then Save, and Deploy.
Run your tests:
# List of languages
curl -i -k -X GET "https://eval.example.com/translate/v1/languages"
# Translate to specified language (German)
curl -i -k -X POST "https://eval.example.com/translate/v1?lang=de" \
-H "Content-Type:application/json" \
-d '{ "text": "Hello world!" }'
# Translate to default language (Spanish)
curl -i -k -X POST "https://eval.example.com/translate/v1" \
-H "Content-Type:application/json" \
-d '{ "text": "Hello world!" }'
Back in the Challenge Lab UI:
- Click the Check my progress button to complete the task.
NOTE In AM-BuildTranslateResponse
, the response payload JSON needs to be on a single line (no spaces or line breaks) in order for the Challenge's assessment checker to pass. Otherwise, you will be presented with the following error, even though tests using manual API calls yield the correct result:
Please create the 'AM-BuildTranslateResponse' AssignMessage policy with the correct configuration and redeploy the API proxy.
-
Publish > API Products > +Create. Add API Product
translate-product
- see products.json-
Public access, automatically approve access requests, available in eval environment
-
Operation to allow access to the
translate-v1
proxy using a path of/
(any request),GET
andPOST
methods, operation quota of 10 requests per 1 minute
-
-
Publish > Developers > +Developer. Create a Developer - see developers.json
-
Publish > Apps > +App. Create a Developer App called
translate-app
with thetranslate-product
API product associated with thejoe@example.com
developer - see developerapps.json- Under the Credentials section shown after creation, click Show on the Key - copy this Key to a safe place (it will be used in testing further down)
-
Add Policy: Security > Verify API Key named
VAK-VerifyKey
which should use theKey
Header. See VAK-VerifyKey.xml -
Add Policy: Traffic Management > Quota named
Q-EnforceQuota
. See Q-EnforceQuota.xml -
On the default Proxy Endpoint, add the PreFlow:
<PreFlow name="PreFlow"> <Request> <Step> <Name>VAK-VerifyKey</Name> </Step> <Step> <Name>Q-EnforceQuota</Name> </Step> </Request> <Response/> </PreFlow>
-
Save and Deploy to eval.
-
Test - Fails (no API key)
curl -i -k -X POST "https://eval.example.com/translate/v1?lang=de" \ -H "Content-Type:application/json" \ -d '{ "text": "Hello world!" }'
-
Test - Fails (invalid API key)
curl -i -k -X POST "https://eval.example.com/translate/v1?lang=de" \ -H "Content-Type:application/json" \ -H "Key: ABC123" \ -d '{ "text": "Hello world!" }'
-
Test - Succeeds (when
$KEY
is set to a valid API Key - get this from the earlier step when setting up a Developer App)export KEY=REPLACEWITHVALIDKEY curl -i -k -X POST "https://eval.example.com/translate/v1?lang=de" \ -H "Content-Type:application/json" \ -H "Key: $KEY" \ -d '{ "text": "Hello world!" }'
-
Click the Check my progress button to complete the task.
NOTE This task currently does NOT pass. A ticket is open with Qwiklabs. The errors returned are:
Please add the 'Q-EnforceQuota' Quota policy with the correct configuration to the proxy endpoint preflow and redeploy the API proxy.
-
Add Policy: Extension > Message Logging named
ML-LogTranslation
- see ML-LogTranslation.xml -
On the default Proxy Endpoint, in the
translate
Flow, after theAM-BuildTranslateResponse
Step, add the following Step in the Response:<Step> <Name>ML-LogTranslation</Name> </Step>
-
Save and Deploy to eval.
-
Test the MessageLogging policy is working:
curl -i -k -X POST "https://eval.example.com/translate/v1?lang=de" \ -H "Content-Type:application/json" \ -H "Key: $KEY" \ -d '{ "text": "Hello world!" }'
-
In the GCP Console, visit the Logging page. In the query area, use the Log name dropdown to select
translate
and click Apply, and then click Run query after issuing the API request. There is a short delay, but the log should appear as follows:de|Hello world!|Hallo Welt!
-
Click the Check my progress button to complete the task.
-
Add Policy: Mediation > AssignMessage named
AM-BuildErrorResponse
- see AM-BuildErrorResponse.xml -
On the default Target Endpoint, add a
FaultRules
section after</HTTPTargetConnection>
as follows:<FaultRules> <FaultRule name="invalid_request_rule"> <Step> <Name>AM-BuildErrorResponse</Name> </Step> <Condition>(fault.name = "ErrorResponseCode")</Condition> </FaultRule> </FaultRules>
-
Save and Deploy to eval.
-
Test - should work
curl -i -k -X POST "https://eval.example.com/translate/v1?lang=de" \ -H "Content-Type:application/json" \ -H "Key: $KEY" \ -d '{ "text": "Hello world!" }'
-
Test - invalid language query parameter, should return the rewritten error message
curl -i -k -X POST "https://eval.example.com/translate/v1?lang=invalid" \ -H "Content-Type:application/json" \ -H "Key: $KEY" \ -d '{ "text": "Hello world!" }'
The error returned should look like this:
{ "error": "Invalid request. Verify the lang query parameter." }
-
Click the Check my progress button to complete the task.