מעבר משירות המשתמשים של App Engine ל-Cloud Identity Platform (מודול 21)

1. סקירה כללית

סדרת הServerless Migration Station של Codelabs (מדריכים מעשיים בקצב עצמי) וסרטונים קשורים נועדו לעזור להעביר מפתחים ללא שרת (serverless) של Google Cloud, באמצעות העברת אפליקציות מדור קודם באמצעות שירותי Google Cloud. כך האפליקציות שלכם יהיו יותר ניידות ויהיו לכם יותר אפשרויות וגמישות, כך שתוכלו להשתלב עם מגוון רחב יותר של מוצרי Cloud ולגשת אליהם בקלות, ולהשדרג בקלות רבה יותר לגרסאות חדשות יותר של שפות. הסדרה מתמקדת בהתחלה במשתמשי Cloud הראשונים, ובעיקר מפתחי App Engine (בסביבה סטנדרטית), אבל היא רחבה מספיק כדי לכלול פלטפורמות אחרות ללא שרת (serverless), כמו Cloud Functions ו-Cloud Run, או במקומות אחרים, אם רלוונטי.

המטרה של ה-Codelab הזה היא להראות למפתחי App Engine של Python 2 איך לעבור מ-App Engine Users API אל Cloud Identity Platform (GCIP). יש גם העברה משתמעת מ-App Engine NDB ל-Cloud NDB בשביל גישה ל-Datastore (מתייחסת בעיקר למודול העברה 2), וגם שדרוג ל-Python 3.

יחידת לימוד 20 מסבירה איך להוסיף את השימוש ב-Users API לאפליקציה לדוגמה של מודול 1. במודול הזה תועברו לאפליקציית מודול 20 המוגמרת ותעבירו את השימוש שלה אל Cloud Identity Platform.

כאן אפשר להבין איך

  • החלפת השימוש בשירות המשתמשים של App Engine ב-Cloud Identity Platform
  • מחליפים את השימוש ב-App Engine NDB ב-Cloud NDB (אפשר לעיין גם במודול 2)
  • הגדרת ספקי זהויות שונים לאימות באמצעות Firebase Auth
  • שימוש ב-Cloud Resource Manager API כדי לקבל פרטי IAM של פרויקט
  • שימוש ב-Firebase Admin SDK כדי לקבל פרטי משתמשים
  • העברה של האפליקציה לדוגמה ל-Python 3

למה תזדקק?

סקר

איך תשתמשו במדריך הזה?

לקריאה בלבד לקרוא אותו ולבצע את התרגילים

איזה דירוג מגיע לדעתך לחוויה שלך עם Python?

מתחילים בינונית בקיאים

איזה דירוג מגיע לדעתך לחוויית השימוש שלך בשירותי Google Cloud?

מתחילים בינונית בקיאים

2. רקע

שירות המשתמשים של App Engine הוא מערכת לאימות משתמשים שמיועדת לשימוש על ידי אפליקציות של App Engine. באמצעות השירות 'כניסה באמצעות חשבון Google' כספק הזהויות, הוא מספק קישורי התחברות והתנתקות נוחים לשימוש באפליקציות, ותומך בקונספט של משתמשים עם הרשאת אדמין ופונקציונליות של אדמין בלבד. כדי לשפר את ניידות האפליקציות, אנחנו ממליצים ל-Google Cloud לעבור משירותים מדור קודם בחבילה של App Engine לשירותים עצמאיים ב-Cloud. למשל, מהשירות Users (משתמשים) אל Cloud Identity Platform.

Identity Platform מבוססת על אימות ב-Firebase ומוסיפה עוד תכונות לארגונים, כולל אימות רב-שלבי, OIDC ו תמיכה ב-SSO SSO, ריבוי דיירים, הסכם רמת שירות של 99.95% ועוד. ההבדלים האלה מודגשים גם בדף ההשוואה בין מוצרי הפלטפורמה לאימות ב-Firebase. שני המוצרים כוללים הרבה יותר תכונות בהשוואה לפונקציונליות שמספקת שירות המשתמשים.

ה-Codelab של מודול 21 הזה מדגים את המעבר של אימות המשתמשים באפליקציה מתכונות של שירות משתמשים לתכונות של Identity Platform, שמשקפות בצורה הכי מדויקת את הפונקציונליות שהודגמה במודול 20. יחידת לימוד 21 כוללת גם העברה מ-App Engine NDB ל-Cloud NDB בשביל גישה ל-Datastore, חזרה על ההעברה של מודול 2.

למרות שקוד מודול 20 'מפורסם' בתור אפליקציה לדוגמה של Python 2, המקור עצמו תואם ל-Python 2 ו-3, והוא נשאר בצורה הזו גם אחרי המעבר ל-Identity Platform (ול-Cloud NDB) במודול 21. אפשר להמשיך להשתמש בשירות Users (משתמשים) בזמן שמשדרגים ל-Python 3, כי ההעברה ל-Identity Platform היא אופציונלית. כדאי לצפות בסרטון ובמודול הקוד של מודול 17 כדי ללמוד איך להמשיך להשתמש בשירותים הכלולים בחבילה ומשדרגים לדור שני, כמו Python 3.

המדריך הזה כולל את השלבים הבאים:

  1. הגדרה/עבודה מוקדמת
  2. עדכון ההגדרות האישיות
  3. שינוי קוד האפליקציה

3. הגדרה/עבודה מוקדמת

בקטע הזה נסביר איך:

  1. הגדרת פרויקט ב-Cloud
  2. אחזור של אפליקציה בסיסית לדוגמה
  3. (מחדש) פריסה ואימות של אפליקציה בסיסית
  4. הפעלת שירותים/ממשקי API חדשים של Google Cloud

מטרת השלבים הבאים היא לוודא שאתם מתחילים עם קוד עבודה שמוכן להעברה לשירותי ענן עצמאיים.

1. הגדרת הפרויקט

אם השלמתם את התהליך של Module 20 Codelab, השתמשו שוב באותו פרויקט (ובקוד). לחלופין, אפשר ליצור פרויקט חדש לגמרי או להשתמש שוב בפרויקט קיים אחר. צריך לוודא שלפרויקט יש חשבון פעיל לחיוב ואפליקציית App Engine מופעלת. תוכלו למצוא את מזהה הפרויקט ולהשתמש בו במהלך ה-Codelab הזה בכל פעם שתיתקלו במשתנה PROJ_ID.

2. אחזור של אפליקציה בסיסית לדוגמה

אחת מהדרישות המוקדמות היא אפליקציית App Engine פעילה של Module 20. לכן, משלימים את Codelab (מומלץ; קישור למעלה) או מעתיקים את קוד המודול 20 מהמאגר. כאן נתחיל ("התחלה"). בין אם תשתמש במודעות שלך או בשלנו, כאן נתחיל. ה-Codelab הזה ינחה אותך לאורך ההעברה, ויסתיים בקוד שדומה למה שמופיע בתיקיית המאגר של מודול 21 (FINISH).

מעתיקים את תיקיית המאגר של מודול 20. הוא אמור להיראות כמו הפלט שבהמשך, ויכול להיות שתופיע בו תיקייה בשם lib אם תבצעו את Codelab במודול 20:

$ ls
README.md               appengine_config.py     templates
app.yaml                main.py                 requirements.txt

3. (מחדש) פריסה ואימות של אפליקציה בסיסית

מבצעים את השלבים הבאים כדי לפרוס את אפליקציית מודול 20:

  1. אם קיימת תיקייה כזו, מוחקים את התיקייה lib ומריצים את הפקודה pip install -t lib -r requirements.txt כדי לאכלס אותה מחדש. יכול להיות שתצטרכו להשתמש ב-pip2 אם מותקנת אצלכם גם Python 2 וגם גרסה 3.
  2. חשוב לוודא שהתקנתם ואתחלתם את כלי שורת הפקודה gcloud ושבדקתם את השימוש בו.
  3. אם לא רוצים להזין את ה-PROJ_ID בכל פעם שפקודת gcloud נוצרת, צריך קודם להגדיר את הפרויקט ב-Cloud ל-gcloud config set project PROJ_ID.
  4. פריסת האפליקציה לדוגמה באמצעות gcloud app deploy
  5. מוודאים שהאפליקציה פועלת כצפוי ללא שגיאות. אם השלמתם את התהליך במודול 20, האפליקציה תציג את פרטי ההתחברות של המשתמש (כתובת האימייל של המשתמש, תג אדמין אפשרי ולחצן התחברות/התנתקות) בחלק העליון לצד הביקורים האחרונים (יש איור שמופיע בהמשך).

907e64c19ef964f8.png

כניסה כמשתמש רגיל גורמת להצגת כתובת האימייל של המשתמש, והפעולה 'התחברות' הלחצן ישתנה ל"התנתקות" לחצן:

ad7b59916b69a035.png

כניסה בתור משתמש עם הרשאת אדמין גורמת לכך שכתובת האימייל של המשתמש תוצג לצד הכיתוב '(אדמין)'. לידו:

867bcb3334149e4.png

4. הפעלת ממשקי API/שירותים חדשים של Google Cloud

מבוא

אפליקציית Module 20 מתבססת על ממשקי ה-API של App Engine NDB ו-Users (משתמשים). יש שירותים בחבילה שלא מצריכים הגדרה נוספת, אבל שירותי Cloud עצמאיים כן. בנוסף, האפליקציה המעודכנת תשתמש גם ב-Cloud Identity Platform וגם ב-Cloud Datastore (דרך ספריית הלקוח של Cloud NDB). בנוסף, כדי לקבוע משתמשים עם הרשאת אדמין ב-App Engine נדרש גם שימוש ב-Cloud Resource Manager API.

עלות

  • האפשרויות של App Engine ו-Cloud Datastore הן חינם תמיד" רמה, וכל עוד לא חורגים מהמגבלות האלה, לא תצברו חיובים על השלמת המדריך הזה. פרטים נוספים זמינים בדף התמחור של App Engine ובדף התמחור של Cloud Datastore.
  • החיוב על השימוש ב-Cloud Identity Platform תלוי במספר המשתמשים הפעילים בחודש (MAU) או באימותי האימות; גרסה מסוימת של 'חינם' זמינים לכל מודל שימוש. פרטים נוספים זמינים בדף המחירים שלו. בנוסף, ב-App Engine וב-Cloud Datastore נדרש חיוב בלבד, אבל השימוש ב-GCIP לבדו לא מחייב הפעלת חיוב כל עוד אתם לא חורגים מהמכסות היומיות ללא אינסטרומנטציה. לכן כדאי להביא את זה בחשבון בפרויקטים ב-Cloud שלא כוללים שירותים או ממשקי API של Cloud שנדרשים להם חיוב.
  • ברוב המקרים, השימוש ב-Cloud Resource Manager API הוא בחינם לפי דף התמחור שלו.

המשתמשים מפעילים את Cloud APIs ממסוף Cloud או משורת הפקודה (באמצעות הפקודה gcloud, חלק מ-Cloud SDK), בהתאם להעדפות שלכם. נתחיל עם ממשקי ה-API של Cloud Datastore ו-Cloud Resource Manager.

ממסוף Cloud

נכנסים אל הדף 'ספרייה' של מנהל ה-API (של הפרויקט הנכון) במסוף Cloud ומחפשים API באמצעות סרגל החיפוש. c7a740304e9d35b.png

מפעילים את ממשקי ה-API האלה:

מאתרים את הלחצן הפעלה של כל API בנפרד ולוחצים עליו — ייתכן שתתבקשו להזין נתוני חיוב. לדוגמה, זה הדף של Resource Manager API:

fc7bd8f4c49d12e5.png

הלחצן ישתנה ל'ניהול' לאחר ההפעלה (בדרך כלל לאחר מספר שניות):

8eca12d6cc7b45b0.png

מפעילים את Cloud Datastore באותו אופן:

83811599b110e46b.png

משורת הפקודה

אמנם זהו מידע חזותי להפעלת ממשקי API מהמסוף, אך יש משתמשים שמעדיפים את שורת הפקודה. אתם מקבלים בונוס נוסף על היכולת להפעיל כמה ממשקי API בבת אחת. שולחים את הפקודה הזו כדי להפעיל גם את ממשקי ה-API של Cloud Datastore וגם את ממשקי Cloud Resource Manager API ולהמתין שהפעולה תסתיים, כפי שמוצג כאן:

$ gcloud services enable cloudresourcemanager.googleapis.com datastore.googleapis.com
Operation "operations/acat.p2-aaa-bbb-ccc-ddd-eee-ffffff" finished successfully.

ייתכן שתתבקשו להזין נתוני חיוב.

העמודות 'כתובות URL' לכל API שצוין בפקודה שלמעלה, נקראים שמות השירות של ה-API, והם מופיעים בחלק התחתון של דף הספרייה של כל API. כדי להפעיל ממשקי Cloud API אחרים באפליקציות שלכם, תוכלו למצוא את שמות השירותים שלהם בדפי ה-API המתאימים. פקודה זו מפרטת את כל שמות השירותים עבור ממשקי ה-API שאפשר להפעיל:

gcloud services list --available --filter="name:googleapis.com".

גם במסוף Cloud וגם בשורת הפקודה, אחרי שתשלימו את השלבים שלמעלה, הדוגמה שלנו תוכל לגשת לממשקי ה-API האלה. השלבים הבאים הם הפעלת Cloud Identity Platform וביצוע השינויים הנדרשים בקוד.

הפעלה והגדרה של Cloud Identity Platform (מסוף Cloud בלבד)

Cloud Identity Platform הוא שירות Marketplace כי הוא מתחבר למשאב מחוץ ל-Google Cloud או תלוי בו, למשל אימות ב-Firebase. בשלב הזה אפשר להפעיל שירותים של Marketplace רק ממסוף Cloud. כך עושים זאת:

  1. נכנסים לדף Cloud Identity Platform ב-Cloud Marketplace ולוחצים על הלחצן Enable שם. אם מתבקשים לשדרג מאימות ב-Firebase, מקבלים גישה לתכונות נוספות, כמו אלה שמתוארות למעלה בקטע רקע. הנה הדף של Marketplace שבו מודגש הלחצן הפעלה: 28475f1c9b29de69.png
  2. לאחר שתפעילו את Identity Platform, ייתכן שתועברו באופן אוטומטי לדף Identity Providers. אם לא, אפשר להשתמש בקישור הנוח הזה כדי להגיע לשם. fc2d92d42a5d1dd7.png
  3. הפעלת ספק האימות של Google. אם לא הוגדרו ספקים, לוחצים על הוספת ספק ובוחרים באפשרות Google. כשחוזרים למסך הזה, הרשומה Google אמורה להיות מופעלת. Google היא ספק האימות היחיד שבו אנחנו משתמשים במדריך זה כדי לשקף את שירות משתמשי App Engine כשירות כניסה פשוט באמצעות Google. באפליקציות שלכם, תוכלו להפעיל ספקי אימות נוספים.
  4. אחרי שבוחרים את ספקי ההרשאה הרצויים ומגדירים את Google, לוחצים על פרטי הגדרת האפליקציה, ומחלון תיבת הדו-שיח שמבטיח, מעתיקים את apiKey ואת authDomain באובייקט config בכרטיסייה 'אינטרנט', ושומרים את שניהם במקום בטוח. למה לא להעתיק את כולו? קטע הקוד בתיבת הדו-שיח הזו כתוב בתוך הקוד ומיושן, לכן פשוט צריך לשמור את הביטים החשובים ביותר ולהשתמש בהם בקוד שלנו עם יותר שימוש בו-זמנית ב-Firebase Auth. אחרי שמעתיקים את הערכים ושומרים אותם במקום בטוח, לוחצים על הלחצן סגירה ומשלימים את כל תהליך ההגדרה הנדרש. bbb09dcdd9be538e.png

4. עדכון ההגדרות האישיות

עדכוני ההגדרות כוללים שינוי של קובצי תצורה שונים וכן יצירת שווה ערך ל-App Engine, אבל בסביבה העסקית של Cloud Identity Platform.

appengine_config.py

  • אם משדרגים ל-Python 3, צריך למחוק את appengine_config.py
  • אם אתם מתכננים לעדכן את Identity Platform אבל נשארים ב-Python 2, אל תמחקו את הקובץ. במקום זאת, אנחנו נעדכן אותו מאוחר יותר במהלך הגיבוי העורפי של Python 2.

requirements.txt

בקובץ requirements.txt של מודול 20 מופיע רק Flask. עבור מודול 21, מוסיפים את החבילות הבאות:

עכשיו התוכן של requirements.txt אמור להיראות כך:

flask
google-auth
google-cloud-ndb
google-cloud-resource-manager
firebase-admin

app.yaml

  • שדרוג ל-Python 3 פירושו קובץ app.yaml פשוט יותר. מסירים את כל התוכן מלבד הוראת זמן הריצה ומגדירים אותה לגרסה 3 של Python שנתמכת כרגע. בדוגמה נעשה כרגע שימוש בגרסה 3.10.
  • אם אתם נשארים עם Python 2, אתם עדיין לא צריכים לבצע כל פעולה.

לפני:

runtime: python27
threadsafe: yes
api_version: 1

handlers:
- url: /.*
  script: main.app

לאפליקציה לדוגמה של מודול 20 אין רכיבי handler של קבצים סטטיים. אם האפליקציות שלך כן, משאירים אותן ללא שינוי. אפשר להסיר את כל הגורמים המטפלים בסקריפטים אם רוצים, או פשוט להשאיר אותם שם לצורך התייחסות כל עוד משנים את הכינויים שלהם ל-auto, כפי שמתואר במדריך להעברת נתונים של app.yaml. בעקבות השינויים האלה, הגרסה המעודכנת של app.yaml עבור Python 3 הופכת לפשוטה יותר:

אחרי:

runtime: python310

עדכוני הגדרות נוספים

לא משנה אם אתם ממשיכים להשתמש ב-Python 2 או מעבירים ל-Python 3, אם יש לכם תיקיית lib, צריך למחוק אותה.

5. שינוי קוד האפליקציה

הקטע הזה כולל עדכונים לקובץ הראשי של האפליקציה, main.py, שמחליף את השימוש בשירות משתמשי App Engine ב-Cloud Identity Platform. לאחר העדכון של האפליקציה הראשית, צריך לעדכן את תבנית האינטרנט, templates/index.html.

ייבוא ואתחול של עדכונים

כדי לעדכן את הייבוא ולאתחל את משאבי האפליקציות, צריך לפעול לפי השלבים הבאים:

  1. לצורך הייבוא, עליך להחליף את App Engine NDB ב-Cloud NDB.
  2. יחד עם Cloud NDB, אפשר גם לייבא את Cloud Resource Manager.
  3. Identity Platform מבוססת על Firebase Auth, לכן מייבאים את Firebase Admin SDK.
  4. ממשקי Cloud API מחייבים שימוש בלקוח API, לכן צריך להפעיל אותו עבור Cloud NDB מתחת לאתחול Flask.

החבילה של Cloud Resource Manager מיובאת לכאן, אבל נשתמש בה בשלב מאוחר יותר באתחול האפליקציה. בהמשך מוצגים הייבוא והאתחול ממודול 20, ולאחר מכן המראה של הקטעים אחרי הטמעת השינויים שלמעלה:

לפני:

from flask import Flask, render_template, request
from google.appengine.api import users
from google.appengine.ext import ndb

app = Flask(__name__)

אחרי:

from flask import Flask, render_template, request
from google.auth import default
from google.cloud import ndb, resourcemanager
from firebase_admin import auth, initialize_app

# initialize Flask and Cloud NDB API client
app = Flask(__name__)
ds_client = ndb.Client()

תמיכה למשתמשים עם הרשאות אדמין ב-App Engine

יש להוסיף לאפליקציה שני רכיבים שתומך בזיהוי של משתמשים עם הרשאת אדמין:

  • _get_gae_admins() – אוסף קבוצה של משתמשים עם הרשאת אדמין; בוצעה קריאה פעם אחת ונשמר
  • is_admin() — בודק אם המשתמש שמחובר לחשבון הוא משתמש עם הרשאת אדמין. בוצעה קריאה בכל התחברות של משתמש

פונקציית השירות, _get_gae_admins(), קוראת לממשק ה-API של מנהל המשאבים כדי לאחזר את Cloud IAM allow-policy הנוכחי. מדיניות ההרשאה מגדירה ואוכפת אילו תפקידים ניתנים לאילו חשבונות משתמשים (משתמשים אנושיים, חשבונות שירות וכו'). ההגדרה כוללת:

  • אחזור מזהה הפרויקט ב-Cloud (PROJ_ID)
  • יצירת לקוח API של מנהל המשאבים (rm_client)
  • יצירת קבוצה (לקריאה בלבד) של תפקידי אדמין App Engine (_TARGETS)

מנהל המשאבים דורש את מזהה הפרויקט ב-Cloud, לכן מייבאים את google.auth.default() וקוראים לפונקציה הזו כדי לקבל את מזהה הפרויקט. הקריאה הזו כוללת פרמטר שנראה כמו כתובת URL, אבל הוא היקף הרשאות OAuth2. כשמריצים אפליקציות בענן, למשל במכונה וירטואלית או באפליקציית App Engine של Compute Engine, מוגדר חשבון שירות שמוגדר כברירת מחדל עם הרשאות רחבות. בהתאם לשיטות המומלצות של הרשאות מינימליות, מומלץ ליצור חשבונות שירות בניהול המשתמשים משלכם.

בקריאות ל-API, מומלץ לצמצם עוד את היקף האפליקציות לרמה המינימלית הדרושה כדי לפעול כראוי. הקריאה ל-API של מנהל המשאבים שנבצע היא get_iam_policy(), שצריכה להיות באחד מההיקפים הבאים:

  • https://www.googleapis.com/auth/cloud-platform
  • https://www.googleapis.com/auth/cloud-platform.read-only
  • https://www.googleapis.com/auth/cloudplatformprojects
  • https://www.googleapis.com/auth/cloudplatformprojects.readonly

לאפליקציה לדוגמה נדרשת רק הרשאת קריאה בלבד למדיניות ההרשאה. הוא לא משנה את המדיניות וגם לא צריך גישה לפרויקט כולו. כלומר, לא נדרשת לאפליקציה אף אחת משלוש ההרשאות הראשונות הנדרשות. השלב האחרון הוא כל מה שנדרש, וזה מה שאנחנו מיישמים עבור האפליקציה לדוגמה.

הגוף הראשי של הפונקציה יוצר קבוצה ריקה של משתמשים עם הרשאת אדמין (admins), מאחזר את allow_policy דרך get_iam_policy() ועובר בלולאה דרך כל הקישורים שמחפשים באופן ספציפי תפקידי אדמין של App Engine:

  • roles/viewer
  • roles/editor
  • roles/owner
  • roles/appengine.appAdmin

בכל תפקיד יעד שנמצא, המערכת אוספת אילו משתמשים שייכים לתפקיד הזה, ומוסיפה אותם לקבוצה הכוללת של משתמשים עם הרשאת אדמין. העדכון מסתיים בהחזרת כל המשתמשים עם הרשאת אדמין שנמצאו ונשמרו במטמון באופן קבוע (_ADMINS) לכל משך החיים של מופע ה-App Engine הזה. השיחה תתחיל בקרוב.

צריך להוסיף את ההגדרה הבאה של פונקציית _get_gae_admins() ל-main.py מתחת לייצוג לקוח ה-API של Cloud NDB (ds_client):

def _get_gae_admins():
    'return set of App Engine admins'
    # setup constants for calling Cloud Resource Manager API
    _, PROJ_ID = default(  # Application Default Credentials and project ID
            ['https://www.googleapis.com/auth/cloudplatformprojects.readonly'])
    rm_client = resourcemanager.ProjectsClient()
    _TARGETS = frozenset((     # App Engine admin roles
            'roles/viewer',
            'roles/editor',
            'roles/owner',
            'roles/appengine.appAdmin',
    ))

    # collate users who are members of at least one GAE admin role (_TARGETS)
    admins = set()                      # set of all App Engine admins
    allow_policy = rm_client.get_iam_policy(resource='projects/%s' % PROJ_ID)
    for b in allow_policy.bindings:     # bindings in IAM allow-policy
        if b.role in _TARGETS:          # only look at GAE admin roles
            admins.update(user.split(':', 1).pop() for user in b.members)
    return admins

כשהמשתמשים מתחברים לאפליקציה, אלה הדברים הבאים:

  1. מתבצעת בדיקה מהירה מתבנית האינטרנט אחרי שמשתמש נכנס ל-Firebase.
  2. כשמצב האימות משתנה בתבנית, מתבצעת קריאה של fetch() בסגנון Ajax אל /is_admin שה-handler שלו הוא הפונקציה הבאה, is_admin().
  3. האסימון המזהה של Firebase מועבר בגוף ה-POST אל is_admin(), ואז הוא מוציא אותו מהכותרות וקורא ל-Firebase Admin SDK כדי לאמת אותו. אם מדובר במשתמש חוקי, צריך לחלץ את כתובת האימייל שלו ולבדוק אם מדובר במשתמש עם הרשאת אדמין.
  4. התוצאה הבוליאנית מוחזרת לתבנית כציון 200.

הוספת is_admin() אל main.py מיד אחרי _get_gae_admins():

@app.route('/is_admin', methods=['POST'])
def is_admin():
    'check if user (via their Firebase ID token) is GAE admin (POST) handler'
    id_token = request.headers.get('Authorization')
    email = auth.verify_id_token(id_token).get('email')
    return {'admin': email in _ADMINS}, 200

כל הקוד משתי הפונקציות נדרש כדי לשכפל את הפונקציונליות שזמינה משירות המשתמשים, ובמיוחד את הפונקציה is_current_user_admin() שלו. הבקשה להפעלת הפונקציה במודול 20 עשתה את כל העבודה הקשה, בניגוד למודול 21 שבו הטמענו פתרון חלופי. החדשות הטובות הן שהאפליקציה כבר לא תלויה בשירות App Engine בלבד, כלומר אפשר להעביר את האפליקציות ל-Cloud Run או לשירותים אחרים. בנוסף, אפשר גם לשנות את ההגדרה של 'משתמש עם הרשאת אדמין' בשביל אפליקציות משלכם פשוט על ידי מעבר לתפקידים הרצויים ב-_TARGETS, ואילו שירות המשתמשים כתוב בתוך הקוד לתפקידי האדמין של App Engine.

הפעלת משתמשים עם הרשאת אדמין ב-App Engine ואימות Firebase ב-Firebase

יכולנו לאתחל את Firebase Auth בחלק העליון לאותה נקודה שבה הופעלה אפליקציית Flask ולקוח Cloud NDB API נוצר, אבל לא היה צורך לעשות זאת עד שכל קודי האדמין היו מוגדרים, ועכשיו אנחנו נמצאים במקום הזה. באותו האופן, אחרי שמגדירים את _get_gae_admins(), צריך להפעיל אותו כדי לשמור במטמון את רשימת המשתמשים עם הרשאת האדמין.

מוסיפים את השורות הבאות ממש מתחת לגוף הפונקציה של is_admin():

# initialize Firebase and fetch set of App Engine admins
initialize_app()
_ADMINS = _get_gae_admins()

עדכונים של מודל הנתונים

מודל הנתונים Visit לא משתנה. כדי לגשת למאגר הנתונים, צריך להשתמש באופן מפורש במנהל ההקשר של הלקוח ב-Cloud NDB API, ds_client.context(). בקוד, המשמעות היא ששיחות Datastore ב-store_visit() וב-fetch_visits() צריכות להיות בתוך בלוקים with של Python. העדכון הזה זהה למודול 2. מבצעים את השינויים כך:

לפני:

class Visit(ndb.Model):
    'Visit entity registers visitor IP address & timestamp'
    visitor   = ndb.StringProperty()
    timestamp = ndb.DateTimeProperty(auto_now_add=True)

def store_visit(remote_addr, user_agent):
    'create new Visit entity in Datastore'
    Visit(visitor='{}: {}'.format(remote_addr, user_agent)).put()

def fetch_visits(limit):
    'get most recent visits'
    return Visit.query().order(-Visit.timestamp).fetch(limit)

אחרי:

class Visit(ndb.Model):
    'Visit entity registers visitor IP address & timestamp'
    visitor   = ndb.StringProperty()
    timestamp = ndb.DateTimeProperty(auto_now_add=True)

def store_visit(remote_addr, user_agent):
    'create new Visit entity in Datastore'
    with ds_client.context():
        Visit(visitor='{}: {}'.format(remote_addr, user_agent)).put()

def fetch_visits(limit):
    'get most recent visits'
    with ds_client.context():
        return Visit.query().order(-Visit.timestamp).fetch(limit)

העברת לוגיקת ההתחברות של משתמשים לתבנית אינטרנט

שירות המשתמשים של App Engine הוא בצד השרת, ואילו Firebase Auth ו-Cloud Identity Platform הם בעיקר בצד הלקוח. כתוצאה מכך, חלק גדול מקוד ניהול המשתמשים באפליקציית מודול 20 עובר לתבנית האינטרנט של מודול 21.

ב-main.py, ההקשר של האתר מעביר חמישה קטעי נתונים חיוניים לתבנית. ארבעת הנתונים הראשונים מקושרים לניהול משתמשים ומשתנים בהתאם לשאלה אם המשתמש מחובר לחשבון או לא:

  • who – כתובת האימייל של המשתמש, אם הוא נכנס לחשבון או אם הוא משתמש אחר
  • admin – תג (אדמין) אם המשתמש שמחובר לחשבון הוא אדמין
  • sign — מציגים את הלחצן התחברות או התנתקות.
  • link – קישורים לכניסה או יציאה בלחיצה על הלחצן
  • visits – הביקורים האחרונים

לפני:

@app.route('/')
def root():
    'main application (GET) handler'
    store_visit(request.remote_addr, request.user_agent)
    visits = fetch_visits(10)

    # put together users context for web template
    user = users.get_current_user()
    context = {  # logged in
        'who':   user.nickname(),
        'admin': '(admin)' if users.is_current_user_admin() else '',
        'sign':  'Logout',
        'link':  '/_ah/logout?continue=%s://%s/' % (
                      request.environ['wsgi.url_scheme'],
                      request.environ['HTTP_HOST'],
                  ),  # alternative to users.create_logout_url()
    } if user else {  # not logged in
        'who':   'user',
        'admin': '',
        'sign':  'Login',
        'link':  users.create_login_url('/'),
    }

    # add visits to context and render template
    context['visits'] = visits  # display whether logged in or not
    return render_template('index.html', **context)

כל ניהול המשתמשים עובר לתבנית האינטרנט, כך שאנחנו נשארים עם הביקורים בלבד, ומחזירים את ה-handler הראשי למה שהיה עד עכשיו באפליקציית מודול 1:

אחרי:

@app.route('/')
def root():
    'main application (GET) handler'
    store_visit(request.remote_addr, request.user_agent)
    visits = fetch_visits(10)
    return render_template('index.html', visits=visits)

עדכון של תבנית האינטרנט

איך נראים כל העדכונים מהקטע הקודם בתבנית? העברת הניהול של המשתמשים מהאפליקציה ל-Firebase Auth שמופעלת בתבנית, ויציאה חלקית של כל הקוד שהעברנו ל-JavaScript. ראינו ירידה קטנה בmain.py, אז אפשר לצפות לצמיחה דומה בtemplates/index.html.

לפני:

<!doctype html>
<html>
<head>
<title>VisitMe Example</title>
</head>
<body>
<p>
Welcome, {{ who }} <code>{{ admin }}</code>
<button id="logbtn">{{ sign }}</button>
</p><hr>

<h1>VisitMe example</h1>
<h3>Last 10 visits</h3>
<ul>
{% for visit in visits %}
    <li>{{ visit.timestamp.ctime() }} from {{ visit.visitor }}</li>
{% endfor %}
</ul>

<script>
document.getElementById("logbtn").onclick = () => {
    window.location.href = '{{ link }}';
};
</script>
</body>
</html>

החלפת תבנית האינטרנט במלואה בתוכן הבא:

אחרי:

<!doctype html>
<html>
<head>
<title>VisitMe Example</title>

<script type="module">
// import Firebase module attributes
import {
        initializeApp
} from "https://www.gstatic.com/firebasejs/9.10.0/firebase-app.js";
import {
        GoogleAuthProvider,
        getAuth,
        onAuthStateChanged,
        signInWithPopup,
        signOut
} from "https://www.gstatic.com/firebasejs/9.10.0/firebase-auth.js";

// Firebase config:
// 1a. Go to: console.cloud.google.com/customer-identity/providers
// 1b. May be prompted to enable GCIP and upgrade from Firebase
// 2. Click: "Application Setup Details" button
// 3. Copy: 'apiKey' and 'authDomain' from 'config' variable
var firebaseConfig = {
        apiKey: "YOUR_API_KEY",
        authDomain: "YOUR_AUTH_DOMAIN",
};

// initialize Firebase app & auth components
initializeApp(firebaseConfig);
var auth = getAuth();
var provider = new GoogleAuthProvider();
//provider.setCustomParameters({prompt: 'select_account'});

// define login and logout button functions
function login() {
    signInWithPopup(auth, provider);
};

function logout() {
    signOut(auth);
};

// check if admin & switch to logout button on login; reset everything on logout
onAuthStateChanged(auth, async (user) => {
    if (user && user != null) {
        var email = user.email;
        who.innerHTML = email;
        logbtn.onclick = logout;
        logbtn.innerHTML = "Logout";
        var idToken = await user.getIdToken();
        var rsp = await fetch("/is_admin", {
                method: "POST",
                headers: {Authorization: idToken}
        });
        var data = await rsp.json();
        if (data.admin) {
            admin.style.display = "inline";
        }
    } else {
        who.innerHTML = "user";
        admin.style.display = "none";
        logbtn.onclick = login;
        logbtn.innerHTML = "Login";
    }
});
</script>
</head>

<body>
<p>
Welcome, <span id="who"></span> <span id="admin"><code>(admin)</code></span>
<button id="logbtn"></button>
</p><hr>

<h1>VisitMe example</h1>
<h3>Last 10 visits</h3>
<ul>
{% for visit in visits %}
    <li>{{ visit.timestamp.ctime() }} from {{ visit.visitor }}</li>
{% endfor %}
</ul>

<script>
var who    = document.getElementById("who");
var admin  = document.getElementById("admin");
var logbtn = document.getElementById("logbtn");
</script>
</body>
</html>

בגוף ה-HTML הזה יש רכיבים רבים, לכן נבחן אותם בנפרד.

ייבוא מ-Firebase

עדיין בתוך הכותרת של מסמך ה-HTML, לאחר מעבר לכותרת הדף, מייבאים את רכיבי Firebase הנדרשים. רכיבי Firebase מחולקים עכשיו לכמה מודולים לשיפור היעילות. הקוד להפעלת Firebase מיובא מהמודול הראשי של אפליקציית Firebase, ובו פונקציות שמנהלות אימות של Firebase, Google כספקית אימות, כניסה לחשבון ויציאה ממנו ושינוי מצב האימות (callback) כל הייבוא הוא ממודול האימות של Firebase:

<!doctype html>
<html>
<head>
<title>VisitMe Example</title>

<script type="module">
// import Firebase module attributes
import {
        initializeApp
} from "https://www.gstatic.com/firebasejs/9.10.0/firebase-app.js";
import {
        GoogleAuthProvider,
        getAuth,
        onAuthStateChanged,
        signInWithPopup,
        signOut
} from "https://www.gstatic.com/firebasejs/9.10.0/firebase-auth.js";

הגדרת Firebase

מוקדם יותר בשלב ההגדרה של Identity Platform במדריך הזה, שמרת את apiKey ואת authDomain בתיבת הדו-שיח פרטי הגדרת האפליקציה. מוסיפים את הערכים האלה למשתנה firebaseConfig בקטע הבא. קישור להוראות נוספות זמין בתגובות:

// Firebase config:
// 1a. Go to: console.cloud.google.com/customer-identity/providers
// 1b. May be prompted to enable GCIP and upgrade from Firebase
// 2. Click: "Application Setup Details" button
// 3. Copy: 'apiKey' and 'authDomain' from 'config' variable
var firebaseConfig = {
        apiKey: "YOUR_API_KEY",
        authDomain: "YOUR_AUTH_DOMAIN",
};

אתחול של Firebase

הקטע הבא מפעיל את Firebase באמצעות פרטי ההגדרות האישיות האלה.

// initialize Firebase app & auth components
initializeApp(firebaseConfig);
var auth = getAuth();
var provider = new GoogleAuthProvider();
//provider.setCustomParameters({prompt: 'select_account'});

ההגדרה הזו מגדירה את היכולת להשתמש ב-Google כספקית אימות, ומספקת אפשרות להוספת הערות להצגת בורר החשבונות, גם אם יש רק חשבון Google אחד רשום בסשן של הדפדפן. במילים אחרות, כשיש לכם כמה חשבונות, מוצג לכם 'בורר החשבונות'. כמצופה: a38369389b7c4c7e.png עם זאת, אם יש רק משתמש אחד בסשן, תהליך ההתחברות הושלם באופן אוטומטי ללא אינטראקציה של המשתמש. (החלון הקופץ נעלם לאחר מכן.) אפשר לאלץ את תיבת הדו-שיח של בורר החשבונות להופיע בפני משתמש אחד (לעומת התחברות מיידית לאפליקציה). לשם כך, מבטלים את הוספת ההערה לשורת הפרמטר המותאם אישית. אם האפשרות מופעלת, גם כניסות של משתמש יחיד יובילו לבחירת בורר החשבונות: b75624cb68d94557.png

פונקציות התחברות והתנתקות

שורות הקוד הבאות מורכבות מהפונקציות של לחיצה על לחצן ההתחברות או ההתנתקות:

// define login and logout button functions
function login() {
    signInWithPopup(auth, provider);
};

function logout() {
    signOut(auth);
};

פעולות של כניסה ויציאה

הקטע הראשי האחרון בבלוק <script> הזה הוא הפונקציה שנדרשת לכל שינוי באימות (כניסה או יציאה).

// check if admin & switch to logout button on login; reset everything on logout
onAuthStateChanged(auth, async (user) => {
    if (user && user != null) {
        var email = user.email;
        who.innerHTML = email;
        logbtn.onclick = logout;
        logbtn.innerHTML = "Logout";
        var idToken = await user.getIdToken();
        var rsp = await fetch("/is_admin", {
                method: "POST",
                headers: {Authorization: idToken}
        });
        var data = await rsp.json();
        if (data.admin) {
            admin.style.display = "inline";
        }
    } else {
        who.innerHTML = "user";
        admin.style.display = "none";
        logbtn.onclick = login;
        logbtn.innerHTML = "Login";
    }
});
</script>
</head>

הקוד במודול 20 שקובע אם לשלוח 'משתמש מחובר' ההקשר של התבנית לעומת 'משתמש לא מחובר' ההקשר מועבר כאן. התנאי שמוצג בראש תוצאות החיפוש בtrue אם המשתמש התחבר בהצלחה, וגרם לפעולות הבאות:

  1. כתובת האימייל של המשתמש מוגדרת להצגה.
  2. הלחצן התחברות ישתנה להתנתקות.
  3. מתבצעת קריאה בסגנון Ajax אל /is_admin כדי לקבוע אם להציג את תג המשתמש של האדמין ב-(admin).

כשהמשתמש מתנתק, התנאי else מבוצע כדי לאפס את כל פרטי המשתמש:

  1. שם המשתמש שהוגדר הוא user
  2. כל תג אדמין הוסר
  3. הלחצן התנתקות השתנה חזרה להתחברות

משתני תבניות

אחרי שקטע הכותרת מסתיים, הגוף הראשי מתחיל במשתני התבנית שמוחלפים ברכיבי HTML שמשתנים לפי הצורך:

  1. שם המשתמש המוצג
  2. תג אדמין ב-(admin) (אם רלוונטי)
  3. הלחצן התחברות או התנתקות
<body>
<p>
Welcome, <span id="who"></span> <span id="admin"><code>(admin)</code></span>
<button id="logbtn"></button>
</p><hr>

ביקורים אחרונים ומשתנים של רכיבי HTML

קוד הביקורים האחרונים לא משתנה, והבלוק <script> האחרון מגדיר את המשתנים עבור רכיבי ה-HTML שמשתנים לכניסה וליציאה, שמפורטים למעלה:

<h1>VisitMe example</h1>
<h3>Last 10 visits</h3>
<ul>
{% for visit in visits %}
    <li>{{ visit.timestamp.ctime() }} from {{ visit.visitor }}</li>
{% endfor %}
</ul>

<script>
var who    = document.getElementById("who");
var admin  = document.getElementById("admin");
var logbtn = document.getElementById("logbtn");
</script>
</body>
</html>

סיימנו את השינויים שנדרשים באפליקציה ובתבנית האינטרנט כדי לעבור מ-App Engine NDB ו-Users APIs ל-Cloud NDB ול-Identity Platform, וגם לשדרג ל-Python 3. מזל טוב, הגעת לאפליקציה החדשה לדוגמה של Module 21. הגרסה שלנו זמינה לבדיקה בתיקיית המאגר של מודול 21b.

החלק הבא ב-Codelab הוא אופציונלי (*) ורק למשתמשים שהאפליקציות שלהם חייבות להישאר ב-Python 2. השלב הזה מוביל את השלבים שצריך לבצע כדי להגיע לאפליקציית Python 2 מלאה של מודול 21.

6. *חזרה אחורה של Python 2

הקטע האופציונלי הזה מיועד למפתחים שמבצעים העברה של Identity Platform, אבל חייבים להמשיך לפעול בסביבת זמן הריצה של Python 2. אם זה לא מדאיג אתכם, דלגו על הקטע הזה.

כדי ליצור גרסת Python 2 פעילה של אפליקציית Module 21, צריך:

  1. דרישות לגבי זמן ריצה: קובצי תצורה שתומכים ב-Python 2 והשינויים הנדרשים באפליקציה הראשית כדי למנוע חוסר תאימות ב-Python 3.
  2. שינוי קטן בספרייה: גרסת Python 2 הוצאה משימוש לפני שחלק מהתכונות הנדרשות נוספו לספריית הלקוח של מנהל המשאבים. לכן, נדרשת דרך חלופית לגשת לפונקציונליות החסרה.

עכשיו נבצע את השלבים האלה. נתחיל בהגדרה.

שחזור של appengine_config.py

מוקדם יותר במדריך הזה, התבקשת למחוק את appengine_config.py כי זמן הריצה של Python 3 App Engine לא נמצא בשימוש. ב-Python 2, לא רק חובה לשמור אותו, אלא גם צריך לעדכן את מודול 20 appengine_config.py כדי שיתמוך בשימוש בספריות מובנות של צד שלישי, כלומר grpcio ו-setuptools. החבילות האלה נדרשות בכל פעם שאפליקציית App Engine משתמשת בספריות לקוח של Cloud, כמו אלה של Cloud NDB ו-Cloud Resource Manager.

הוספת החבילות האלה אל app.yaml תתבצע בעוד רגע, אבל כדי שהאפליקציה תוכל לגשת אליהן, חובה לקרוא לפונקציה pkg_resources.working_set.add_entry() מ-setuptools. כך מתאפשרת תקשורת עם ספריות מובנות של ספריות של צד שלישי (באריזה עצמית או שסופקו על ידי ספק) המותקנות בתיקייה lib.

מטמיעים את העדכונים הבאים בקובץ appengine_config.py כדי שהשינויים האלה ייכנסו לתוקף:

לפני:

from google.appengine.ext import vendor

# Set PATH to your libraries folder.
PATH = 'lib'
# Add libraries installed in the PATH folder.
vendor.add(PATH)

הקוד הזה לבדו לא מספיק כדי לתמוך בשימוש ב-setuptools וב-grpcio. נדרשות עוד כמה שורות, לכן צריך לעדכן את appengine_config.py כדי שהוא ייראה כך:

אחרי:

import pkg_resources
from google.appengine.ext import vendor

# Set PATH to your libraries folder.
PATH = 'lib'
# Add libraries installed in the PATH folder.
vendor.add(PATH)
# Add libraries to pkg_resources working set to find the distribution.
pkg_resources.working_set.add_entry(PATH)

פרטים נוספים על השינויים הנדרשים כדי לתמוך בספריות לקוח של Cloud זמינים במסמכי התיעוד בנושא העברה של שירותים בחבילה.

app.yaml

בדומה ל-appengine_config.py, צריך להחזיר את הקובץ app.yaml לקובץ שתומך ב-Python 2. נתחיל במודול המקורי 20 app.yaml:

לפני:

runtime: python27
threadsafe: yes
api_version: 1

handlers:
- url: /.*
  script: main.app

בנוסף ל-setuptools ול-grpcio כפי שצוין קודם, קיימת תלות (שאינה קשורה באופן מפורש להעברה של Identity Platform) שמחייבת שימוש בספריית הלקוח של Cloud Storage, וכתוצאה יש צורך בחבילה מובנית נוספת של צד שלישי, ssl. להוסיף את כל השלושה בקטע חדש של libraries ולבחור באפשרות 'העדכנית ביותר' הזמינות של החבילות האלה, אל app.yaml:

אחרי:

runtime: python27
threadsafe: yes
api_version: 1

handlers:
- url: /.*
  script: main.app

libraries:
- name: grpcio
  version: latest
- name: setuptools
  version: latest
- name: ssl
  version: latest

requirements.txt

עבור מודול 21, הוספנו את Google Auth, Cloud NDB, Cloud Resource Manager ו-Firebase Admin SDK ל-Python 3 requirements.txt. המצב ב-Python 2 מורכב יותר:

  • ה-API של מנהל המשאבים מספק את הפונקציונליות של מדיניות ההרשאה הנדרשת עבור האפליקציה לדוגמה. לצערנו, התמיכה הזו עדיין לא הייתה זמינה בגרסת Python 2 הסופית של ספריית הלקוח של Cloud Resource Manager. (היא זמינה רק בגרסת Python 3).
  • לכן, נדרשת דרך חלופית לגשת לתכונה הזו מה-API. הפתרון הוא שימוש בספריית הלקוח של Google APIs ברמה נמוכה יותר כדי לתקשר עם ה-API. כדי לעבור לספריית הלקוח הזו, צריך להחליף את google-cloud-resource-manager בחבילה של google-api-python-client ברמה נמוכה יותר.
  • מכיוון ש-Python 2 הוצא משימוש, תרשים התלות שתומך במודול 21 מחייב לנעול חבילות מסוימות לגרסאות ספציפיות. צריך לקרוא לחבילות מסוימות גם אם הן לא צוינו ב-app.yaml של Python 3.

לפני:

flask

החל ממודול 20 requirements.txt, מעדכנים אותו לגרסה הבאה עבור אפליקציה של מודול פעיל 21:

אחרי:

grpcio==1.0.0
protobuf<3.18.0
six>=1.13.0
flask
google-gax<0.13.0
google-api-core==1.31.1
google-api-python-client<=1.11.0
google-auth<2.0dev
google-cloud-datastore==1.15.3
google-cloud-firestore==1.9.0
google-cloud-ndb
google-cloud-pubsub==1.7.0
firebase-admin

מספרי החבילה והגרסה יעודכנו במאגר בהתאם לשינויים ביחסי התלות, אבל ה-app.yaml הזה מספיק לאפליקציה שפועלת בזמן כתיבת הנתונים.

עדכוני הגדרות נוספים

אם לא מחקת את התיקייה lib מקודם, ב-Codelab הזה, עליך לעשות זאת עכשיו. בגרסה requirements.txt המעודכנת, צריך לשלוח את הפקודה המוכרת הבאה כדי להתקין את הדרישות הבאות ב-lib:

pip install -t lib -r requirements.txt  # or pip2

אם מותקנת במערכת הפיתוח שלכם גם Python 2 וגם 3, יכול להיות שאתם צריכים להשתמש ב-pip2 במקום ב-pip.

שינוי קוד האפליקציה

למרבה המזל, רוב השינויים הנדרשים נמצאים בקובצי התצורה. השינוי היחיד שנדרש בקוד האפליקציה הוא עדכון קל שיאפשר להשתמש בספריית הלקוח של Google API ברמה נמוכה יותר במקום בספריית הלקוח של מנהל המשאבים כדי לגשת ל-API. לא נדרשים עדכונים לתבנית האינטרנט templates/index.html.

עדכון ייבוא ואתחול

מחליפים את ספריית הלקוח של מנהל המשאבים (google.cloud.resourcemanager) בספריית הלקוח של Google APIs (googleapiclient.discovery), כפי שמוצג בהמשך:

לפני:

from flask import Flask, render_template, request
from google.auth import default
from google.cloud import ndb, resourcemanager
from firebase_admin import auth, initialize_app

אחרי:

from flask import Flask, render_template, request
from google.auth import default
from google.cloud import ndb
from googleapiclient import discovery
from firebase_admin import auth, initialize_app

תמיכה למשתמשים עם הרשאת אדמין ב-App Engine

כדי לתמוך בספריית הלקוח ברמה נמוכה יותר, צריך לבצע כמה שינויים ב-_get_gae_admins(). קודם נסביר מה עומד להשתנות ואז ניתן לך את כל הקוד שצריך לעדכן.

כדי להשתמש בקוד Python 2, צריך להשתמש גם בפרטי הכניסה וגם במזהה הפרויקט שמוחזר מ-google.auth.default(). פרטי הכניסה לא נמצאים בשימוש ב-Python 3, לכן הם הוקצו למשתנה דמה גנרי של קו תחתון ( _ ). מכיוון שהוא נדרש לגרסת Python 2, צריך לשנות את הקו התחתון ל-CREDS. בנוסף, במקום ליצור לקוח API של מנהל המשאבים, צריך ליצור נקודת קצה (endpoint) של שירות API, בדומה ללקוח API, ולכן נשאיר את אותו שם משתנה (rm_client). אחד ההבדלים האפשריים הוא שיצירה של נקודת קצה (endpoint) של שירות דורשת פרטי כניסה (CREDS).

שינויים אלה באים לידי ביטוי בקוד הבא:

לפני:

_, PROJ_ID = default(  # Application Default Credentials and project ID
        ['https://www.googleapis.com/auth/cloudplatformprojects.readonly'])
rm_client = resourcemanager.ProjectsClient()

אחרי:

CREDS, PROJ_ID = default(  # Application Default Credentials and project ID
        ['https://www.googleapis.com/auth/cloud-platform'])
rm_client = discovery.build('cloudresourcemanager', 'v1', credentials=CREDS)

ההבדל השני הוא שספריית הלקוח של מנהל המשאבים מחזירה אובייקטים של מדיניות ההרשאה שמשתמשים בסימון של מאפיין מנוקד, ואילו ספריית הלקוח ברמה הנמוכה יותר מחזירה מילוני Python שבהם משתמשים בסוגריים מרובעים ( [ ] ). לדוגמה, נעשה שימוש ב-binding.role בספריית הלקוח של מנהל המשאבים לעומת ב-binding['role'] בספרייה ברמה נמוכה יותר. האפשרות הראשונה כוללת גם את הטקסט 'קווים תחתונים_מופרדים' לעומת הספרייה ברמה נמוכה יותר עם העדפה ל-"CamelCased" ודרך קצת שונה להעביר פרמטרים של API.

ההבדלים האלה בשימוש מוצגים למטה:

לפני:

allow_policy = rm_client.get_iam_policy(resource='projects/%s' % PROJ_ID)
for b in allow_policy.bindings:     # bindings in IAM allow-policy
    if b.role in _TARGETS:          # only look at GAE admin roles
        admins.update(user.split(':', 1).pop() for user in b.members)

אחרי:

allow_policy = rm_client.projects().getIamPolicy(resource=PROJ_ID).execute()
for b in allow_policy['bindings']:  # bindings in IAM allow-policy
    if b['role'] in _TARGETS:       # only look at GAE admin roles
        admins.update(user.split(':', 1).pop() for user in b['members'])

לאחר השילוב של כל השינויים האלה, צריך להחליף את _get_gae_admins() של Python 3 בגרסה המקבילה הבאה של Python 2:

def _get_gae_admins():
    'return set of App Engine admins'
    # setup constants for calling Cloud Resource Manager API
    CREDS, PROJ_ID = default(  # Application Default Credentials and project ID
            ['https://www.googleapis.com/auth/cloud-platform'])
    rm_client = discovery.build('cloudresourcemanager', 'v1', credentials=CREDS)
    _TARGETS = frozenset((     # App Engine admin roles
            'roles/viewer',
            'roles/editor',
            'roles/owner',
            'roles/appengine.appAdmin',
    ))

    # collate users who are members of at least one GAE admin role (_TARGETS)
    admins = set()                      # set of all App Engine admins
    allow_policy = rm_client.projects().getIamPolicy(resource=PROJ_ID).execute()
    for b in allow_policy['bindings']:  # bindings in IAM allow-policy
        if b['role'] in _TARGETS:       # only look at GAE admin roles
            admins.update(user.split(':', 1).pop() for user in b['members'])
    return admins

הפונקציה is_admin() לא דורשת עדכונים כי היא מסתמכת על _get_gae_admins(), שכבר עודכן.

סיימנו את השינויים הנדרשים כדי לבצע העברה לאחור של אפליקציית Python 3 Module 21 אל Python 2. מזל טוב, קיבלת את האפליקציה המעודכנת של מודול 21 לדוגמה. כל הקוד יופיע בתיקיית המאגר של מודול 21a.

7. סיכום/ניקוי

השלבים האחרונים ב-Codelab הם לוודא שלחשבונות משתמשים (משתמשים או חשבונות שירות) שמפעילים את האפליקציה הזו יש את ההרשאות המתאימות כדי לעשות זאת, ואז לפרוס את האפליקציה כדי לוודא שהיא פועלת באופן הרצוי ושהשינויים יבואו לידי ביטוי בפלט.

יכולת לקרוא את מדיניות ההרשאה של IAM

מוקדם יותר הצגנו את ארבעת התפקידים הנדרשים כדי להיות מוכרים כמשתמש עם הרשאת אדמין ב-App Engine, אבל עכשיו יש תפקיד חמישי להיכרות עם:

  • roles/viewer
  • roles/editor
  • roles/owner
  • roles/appengine.appAdmin
  • roles/resourcemanager.projectIamAdmin (לחשבונות משתמשים שניגשים למדיניות ההרשאה של IAM)

התפקיד roles/resourcemanager.projectIamAdmin מאפשר לחשבונות משתמשים לקבוע אם משתמש קצה חבר באחד מתפקידי האדמין של App Engine. ללא מינוי ל-roles/resourcemanager.projectIamAdmin, קריאות ל-Cloud Resource Manager API כדי לקבל את מדיניות ההרשאה ייכשלו.

אין צורך לבצע כאן כל פעולה מפורשת, כי האפליקציה שלך תפעל בחשבון השירות שמשמש כברירת מחדל ב-App Engine, שמקבל אוטומטית חברות בתפקיד הזה. גם אם בשלב הפיתוח משתמשים בחשבון השירות שמוגדר כברירת מחדל, מומלץ מאוד ליצור חשבון שירות בניהול המשתמשים ולהשתמש בו. ההרשאות האלה הנדרשות כדי שהאפליקציה תפעל כראוי. כדי להעניק חברות לחשבון שירות כזה, מריצים את הפקודה הבאה:

$ gcloud projects add-iam-policy-binding PROJ_ID --member="serviceAccount:USR_MGD_SVC_ACCT@PROJ_ID.iam.gserviceaccount.com" --role=roles/resourcemanager.projectIamAdmin

PROJ_ID הוא מזהה הפרויקט ב-Cloud ו-USR_MGD_SVC_ACCT@PROJ_ID.iam.gserviceaccount.com הוא חשבון השירות המנוהל על ידי המשתמש שאתם יוצרים לאפליקציה. הפקודה הזו יוצרת את פלט המדיניות המעודכנת של IAM בשביל הפרויקט שלכם, ומאפשרת לכם לוודא שלחשבון השירות יש חברות ב-roles/resourcemanager.projectIamAdmin. מידע נוסף זמין במאמרי העזרה. שוב ושוב, לא צריך להנפיק את הפקודה הזו ב-Codelab הזה, אלא לשמור אותה כחומר עזר לעדכון האפליקציות שלכם.

פריסה ואימות של אפליקציה

מעלים את האפליקציה לענן באמצעות פקודת gcloud app deploy הרגילה. לאחר הפריסה, הפונקציונליות אמורה כמעט זהה לזו של אפליקציית מודול 20, פרט לכך שהחלפתם בהצלחה את שירות משתמשי App Engine ב-Cloud Identity Platform (ו-Firebase Auth) לניהול משתמשים:

3a83ae745121d70.png

הבדל אחד שתוכלו לראות בהשוואה למודול 20 הוא שלחיצה על תוצאות ההתחברות בחלון קופץ במקום בהפניה לכתובת אחרת, כפי שתועדה בחלק מצילומי המסך הבאים. עם זאת, בדומה למודול 20, ההתנהגות משתנה מעט בהתאם למספר חשבונות Google שנרשמו בדפדפן.

אם אין משתמשים שנרשמו בדפדפן או משתמש יחיד שעדיין לא נכנסו לחשבון, מופיע חלון קופץ כללי לכניסה באמצעות חשבון Google:

8437f5f3d489a942.png

אם משתמש אחד רשום בדפדפן שלכם אבל נכנס לחשבון במקום אחר, לא תופיע תיבת דו-שיח (או שהיא תקפוץ ותיסגר מיד), והאפליקציה תעבור למצב מחובר (המערכת תציג את כתובת האימייל של המשתמש ואת הלחצן התנתקות).

ייתכן שמפתחים מסוימים ירצו להשתמש בבורר חשבונות, גם למשתמש יחיד:

b75624cb68d94557.png

כדי ליישם את זה, צריך לבטל את התגובה לשורה provider.setCustomParameters({prompt: 'select_account'}); בתבנית האינטרנט כפי שמתואר קודם.

אם יש יותר ממשתמש אחד, קופצת תיבת הדו-שיח של בוחר החשבונות (ראו בהמשך). אם המשתמש עדיין לא מחובר לחשבון, תוצג בקשה לכך. אם כבר נכנסתם לחשבון, החלון הקופץ ייעלם והאפליקציה תעבור למצב מחובר.

c454455b6020d5e4.png

מצב הכניסה של מודול 21 נראה זהה לממשק המשתמש של מודול 20:

49ebe4dcc1eff11f.png

הכלל הזה נכון גם כאשר משתמש עם הרשאת אדמין נכנס לחשבון:

44302f35b39856eb.png

בניגוד למודול 21, מודול 20 תמיד ניגש ללוגיקה של תוכן תבנית האינטרנט מהאפליקציה (קוד בצד השרת). אחד החסרונות במודול 20 הוא שביקור אחד נרשם כשמשתמש הקצה לוחץ על האפליקציה בפעם הראשונה, וביקור אחר נרשם כשהמשתמש נכנס לחשבון.

במודול 21, לוגיקת ההתחברות מתרחשת רק בתבנית האינטרנט (קוד בצד הלקוח). לא נדרשת נסיעה בצד השרת כדי לקבוע איזה תוכן להציג. הקריאה היחידה לשרת היא בדיקה של משתמשים עם הרשאת אדמין אחרי הכניסה של משתמש הקצה. פירוש הדבר שכניסות והתנתקות לא תספור ביקורים נוספים, כך שרשימת הביקורים העדכנית ביותר נשארת קבועה בפעולות של ניהול משתמשים. שימו לב שבצילומי המסך שלמעלה מוצגת אותה קבוצה של ארבעה ביקורים בכמה כניסות של משתמשים.

צילומי המסך של מודול 20 מדגימים את "הבאג של ביקור כפול". בתחילת ה-Codelab הזה. יומני ביקורים נפרדים מוצגים עבור כל פעולת כניסה או יציאה. כדאי לבדוק את חותמות הזמן של הביקור האחרון בכל צילום מסך שבו מופיעה הסדר הכרונולוגי.

הסרת המשאבים

כללי

אם סיימתם בינתיים, מומלץ להשבית את אפליקציית App Engine כדי להימנע מצבירת חיובים. עם זאת, אם אתם רוצים להתנסות בפיצ'רים נוספים או להתנסות בהם, בפלטפורמת App Engine יש מכסה בחינם, וכל עוד אתם לא חורגים ממכסת השימוש הזו, לא נחייב אתכם. הבקשה נועדה למחשוב, אבל ייתכן שיחולו חיובים גם על שירותים רלוונטיים של App Engine. למידע נוסף, יש לעיין בדף התמחור של האפליקציה. אם ההעברה הזו כוללת שירותי ענן אחרים, הם מחויבים בנפרד. בכל מקרה, אם רלוונטי, ראו "ספציפי ל-Codelab זה" שבהמשך.

גילוי נאות מלא, פריסה בפלטפורמת מחשוב ללא שרת (serverless) של Google Cloud, כמו App Engine, כרוכה בעלויות נמוכות של build ואחסון. ל-Cloud Build יש מכסה משלה בחינם, כמו גם ל-Cloud Storage. נפח האחסון של התמונה הזו תופס חלק מהמכסה. עם זאת, ייתכן שאתם גרים באזור שאין בו תוכנית ללא תשלום כזה, לכן כדאי שתהיו מודעים לשימוש שלכם בנפח האחסון כדי למזער את העלויות הפוטנציאליות. 'תיקיות' ספציפיות של Cloud Storage כדאי לבדוק:

  • console.cloud.google.com/storage/browser/LOC.artifacts.PROJECT_ID.appspot.com/containers/images
  • console.cloud.google.com/storage/browser/staging.PROJECT_ID.appspot.com
  • הקישורים לנפח האחסון שלמעלה תלויים במאפיין PROJECT_ID ובמאפיין LOC שלך, לדוגמה, "us" אם האפליקציה מתארחת בארה"ב.

מצד שני, אם אתם לא מתכוונים להמשיך להשתמש באפליקציה הזו או ב-Codelabs קשורים אחרים ורוצים למחוק את הכול לחלוטין, כדאי להשבית את הפרויקט.

ספציפי ל-Codelab הזה

השירותים שמפורטים בהמשך הם ייחודיים ל-Codelab הזה. אפשר לקרוא מידע נוסף במסמכי התיעוד של כל מוצר:

השלבים הבאים

מעבר למדריך הזה, יש מודולים אחרים להעברה שמתמקדים בהפסקת השימוש בשירותים בחבילה מהדור הקודם:

App Engine היא כבר לא הפלטפורמה היחידה ללא שרת (serverless) ב-Google Cloud. אם יש לכם אפליקציה קטנה של App Engine או אפליקציה שיש לה פונקציונליות מוגבלת ואתם רוצים להפוך אותה למיקרו-שירות (microservice) עצמאי, או שאתם רוצים לפצל אפליקציה מונוליתית למספר רכיבים לשימוש חוזר, כדאי לשקול לעבור ל-Cloud Functions. אם יצירת קונטיינרים הפכה לחלק מתהליך פיתוח האפליקציות שלכם, במיוחד אם היא מורכבת מצינור עיבוד נתונים של CI/CD (אינטגרציה רציפה (CI/CD)/פיתוח רציף (continuous delivery) או פריסה), מומלץ לעבור ל-Cloud Run. התרחישים האלה מתוארים במודולים הבאים:

  • מעבר מ-App Engine ל-Cloud Functions: ראו מודול 11
  • מעבר מ-App Engine ל-Cloud Run: אפשר לעיין במודול 4 ליצירת קונטיינרים לאפליקציה באמצעות Docker, או במודול 5 כדי לבצע אותו ללא קונטיינרים, ידע ב-Docker או Dockerfile

המעבר לפלטפורמה אחרת ללא שרת הוא אופציונלי, ומומלץ לבדוק את האפשרויות הטובות ביותר לאפליקציות ולתרחישים לדוגמה לפני שמבצעים שינויים.

בלי קשר למודול ההעברה הרצוי, כל התוכן של תחנת המיגרציה ללא שרת (serverless) (codelabs, סרטונים, קוד מקור [אם הוא זמין]) יהיה זמין במאגר הקוד הפתוח שלו. README של המאגר גם מספק הדרכה לגבי ההעברות שכדאי לשקול ו"הזמנה" רלוונטית של מודולי העברה.

8. מקורות מידע נוספים

בהמשך מפורטים מקורות מידע נוספים למפתחים שבוחנים את מודולי ההעברה האלה או את מודולי ההעברה שקשורים אליהם. בהמשך תוכלו לספק משוב על התוכן הזה, למצוא קישורים אל הקוד ומסמכי תיעוד שימושיים נוספים.

בעיות/משוב על Codelabs

אם נתקלתם בבעיות ב-Codelab הזה, צריך קודם לחפש את הבעיה לפני השליחה. קישורים לחיפוש וליצירת בעיות חדשות:

משאבים להעברה

בטבלה שלמטה מופיעים הקישורים לתיקיות המאגר של מודול 20 (START) ומודול 21 (FINISH).

Codelab

ֶPython 2

ֶPython 3

יחידת לימוד 20

קוד

(לא רלוונטי)

יחידת לימוד 21 (Codelab זה)

קוד

קוד

הפניות אונליין

בהמשך מוצגים מקורות מידע שרלוונטיים למדריך הזה:

Cloud Identity Platform ו-Cloud Marketplace

Cloud Resource Manager, Cloud IAM, Firebase Admin SDK

משתמשי App Engine, App Engine NDB, Cloud NDB, Cloud Datastore

הפניות אחרות למודול העברה

העברה של App Engine

פלטפורמת App Engine

Cloud SDK

מידע אחר בענן

סרטונים

רישיון

היצירה הזו בשימוש ברישיון Creative Commons Attribution 2.0 גנרי.