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

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

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

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

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

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

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

הדרישות

סקר

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

רק לקרוא לקרוא ולהשלים את התרגילים

איך היית מדרג את חוויית השימוש שלך ב-Python?

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

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

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

2. רקע

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

‫Identity Platform מבוסס על אימות ב-Firebase, וכולל מספר תכונות לארגונים, כולל אימות רב-שלבי, תמיכה ב-SSO ב-OIDC וב-SAML, ריבוי דיירים, הסכם רמת שירות (SLA) של 99.95% ועוד. ההבדלים האלה מודגשים גם בדף השוואת המוצרים Identity Platform ואימות ב-Firebase. לשני המוצרים יש הרבה יותר תכונות מאשר הפונקציונליות שמספק שירות Users.

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

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

במדריך הזה נסביר איך:

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

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

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

  1. הגדרת פרויקט בענן
  2. קבלת אפליקציה לדוגמה של ערך בסיס
  3. (Re)Deploy and validate baseline app
  4. הפעלת שירותים או ממשקי API חדשים של Google Cloud

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

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

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

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

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

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

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

3. (Re)Deploy and validate baseline app

כדי לפרוס את אפליקציית מודול 20:

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

907e64c19ef964f8.png

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

ad7b59916b69a035.png

כשמתחברים בתור משתמש אדמין, כתובת האימייל של המשתמש מוצגת עם הכיתוב (אדמין) לצדה:

867bcb3334149e4.png

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

מבוא

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

עלות

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

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

מתוך Cloud Console

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

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

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

fc7bd8f4c49d12e5.png

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

8eca12d6cc7b45b0.png

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

83811599b110e46b.png

משורת הפקודה

הפעלת ממשקי API דרך המסוף מספקת מידע ויזואלי, אבל יש אנשים שמעדיפים להשתמש בשורת הפקודה. בנוסף, תוכלו להפעיל כמה ממשקי API שתרצו בבת אחת. מריצים את הפקודה הזו כדי להפעיל את Cloud Datastore API ואת 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".

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

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

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

  1. עוברים אל הדף של Cloud Identity Platform ב-Cloud Marketplace ולוחצים על הלחצן Enable. אם מוצגת לכם בקשה לשדרג מ-Firebase Authentication, כדאי לעשות זאת כדי לקבל גישה לתכונות נוספות, כמו אלה שמתוארות בקטע רקע. בדף של זירת המסחר לאפליקציות מודגש הכפתור הפעלה: 28475f1c9b29de69.png
  2. אחרי שמפעילים את Identity Platform, יכול להיות שתועברו אוטומטית לדף ספקי זהויות. אם לא, אפשר להשתמש בקישור הנוח הזה כדי להגיע לשם. fc2d92d42a5d1dd7.png
  3. הפעלת ספק Google Auth. אם לא הוגדרו ספקים, לוחצים על הוספת ספק ובוחרים באפשרות Google. כשחוזרים למסך הזה, הערך Google אמור להיות מופעל. ‫Google היא ספק האימות היחיד שבו אנחנו משתמשים במדריך הזה כדי לשקף את שירות Users של App Engine כשירות קל משקל של כניסה באמצעות חשבון Google. באפליקציות שלכם, אתם יכולים להפעיל ספקי אימות נוספים.
  4. אחרי שבוחרים ומגדירים את Google ואת ספקי האימות הרצויים האחרים, לוחצים על Application Setup Details (פרטי הגדרת האפליקציה). בחלון הדו-שיח שמופיע, מעתיקים את הערכים apiKey ו-authDomain באובייקט config בכרטיסייה Web (אינטרנט) ושומרים אותם במקום בטוח. למה לא להעתיק את הכל? הקטע שמוצג בתיבת הדו-שיח מוצפן וכולל תאריך, לכן כדאי לשמור רק את החלקים החשובים ביותר ולהשתמש בהם בקוד שלנו עם שימוש מקביל רב יותר ב-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. מסירים את כל מה שמופיע בקובץ מלבד ההנחיה של זמן הריצה, ומגדירים אותה לגרסה נתמכת של Python 3. בדוגמה נעשה כרגע שימוש בגרסה 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 Users service מוחלף ב-Cloud Identity Platform. אחרי עדכון האפליקציה הראשית, מעדכנים את תבנית האינטרנט, templates/index.html.

עדכון ייבוא והפעלה

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

  1. לייבוא, מחליפים את App Engine NDB ב-Cloud NDB.
  2. בנוסף ל-Cloud NDB, מייבאים גם את Cloud Resource Manager.
  3. ‫Identity Platform מבוסס על Firebase Auth, לכן צריך לייבא את SDK של Firebase לאדמינים.
  4. כדי להשתמש ב-Cloud APIs, צריך להפעיל לקוח 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(), קוראת ל-Resource Manager API כדי לאחזר את מדיניות ההרשאות של Cloud IAM הנוכחית. מדיניות ההרשאות מגדירה ואוכפת אילו תפקידים ניתנים לאילו חשבונות משתמשים (משתמשים אנושיים, חשבונות שירות וכו'). ההגדרה כוללת:

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

מנהל המשאבים דורש את מזהה הפרויקט בענן, לכן צריך לייבא את google.auth.default() ולהפעיל את הפונקציה הזו כדי לקבל את מזהה הפרויקט. הקריאה הזו כוללת פרמטר שנראה כמו כתובת URL, אבל הוא היקף הרשאות OAuth2. כשמריצים אפליקציות בענן, למשל במכונה וירטואלית ב-Compute Engine או באפליקציית App 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 ממש מתחת ליצירת מופע של לקוח Cloud NDB API ‏ (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. כשמצב האימות משתנה בתבנית, מתבצעת קריאה בסגנון Ajax אל /is_admin, והמטפל שלה הוא הפונקציה הבאה, is_admin().fetch()
  3. אסימון המזהה של Firebase מועבר בגוף של בקשת POST אל is_admin(), ששולף אותו מהכותרות וקורא ל-SDK של Firebase לאדמינים כדי לאמת אותו. אם מדובר במשתמש תקין, מחלצים את כתובת האימייל שלו ובודקים אם הוא משתמש עם הרשאות אדמין.
  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

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

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

יכולנו לאתחל את 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 לא משתנה. כדי לגשת ל-Datastore, צריך להשתמש במנהל ההקשר של לקוח Cloud NDB API,‏ ds_client.context(). בקוד, המשמעות היא שצריך לעטוף את הקריאות ל-Datastore גם ב-store_visit() וגם ב-fetch_visits() בתוך בלוקים של Python with. העדכון הזה זהה לעדכון של מודול 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)

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

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

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

  • who — כתובת האימייל של המשתמש אם הוא מחובר לחשבון, או user אחרת
  • 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)

כל ניהול המשתמשים עובר לתבנית האינטרנט, ולכן נשארנו רק עם הביקורים, והחזרנו את המטפל הראשי למצב שהיה לנו באפליקציה של מודול 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 Auth:

<!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 ומממשקי ה-API של Users אל Cloud NDB ו-Identity Platform, וגם לשדרג ל-Python 3. ברכות על ההגעה לאפליקציית הדוגמה החדשה של מודול 21! הגרסה שלנו זמינה לבדיקה בתיקיית המאגר של מודול 21b.

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

6. *Python 2 backport

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

כדי ליצור גרסה תקינה של אפליקציית מודול 21 ב-Python 2, צריך את הפריטים הבאים:

  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. כך ספריות צד שלישי שהועתקו (בשיטת self-bundled או vendored) והותקנו בתיקייה 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 ו-SDK של Firebase לאדמינים ל-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 צריך לנעול חבילות מסוימות לגרסאות ספציפיות. צריך לציין חלק מהחבילות גם אם הן לא מוגדרות ב-Python 3 app.yaml.

לפני:

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 וגם Python 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 Admin

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

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

ההבדל הנוסף הוא שספריית הלקוח של Resource Manager מחזירה אובייקטים של מדיניות הרשאה שמשתמשים בסימון של מאפיינים עם נקודות, בעוד שספריית הלקוח ברמה הנמוכה יותר מחזירה מילונים של Python שבהם נעשה שימוש בסוגריים מרובעים ( [ ] ). לדוגמה, משתמשים ב-binding.role בספריית הלקוח של Resource Manager לעומת binding['role'] בספרייה ברמה הנמוכה יותר. בספרייה הראשונה נעשה שימוש בשמות עם קו תחתון בין המילים (underscore_separated), לעומת הספרייה ברמה הנמוכה יותר שבה נעשה שימוש בשמות בפורמט CamelCase, ובנוסף יש הבדל קל בדרך שבה מעבירים פרמטרים של 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 הוא מזהה פרויקט בענן ו-USR_MGD_SVC_ACCT@PROJ_ID.iam.gserviceaccount.com הוא חשבון השירות בניהול המשתמשים שאתם יוצרים עבור האפליקציה. הפקודה הזו מחזירה את מדיניות ה-IAM המעודכנת של הפרויקט, שבה אפשר לוודא שחשבון השירות הוא חבר ב-roles/resourcemanager.projectIamAdmin. מידע נוסף מופיע במאמרי העזרה. שוב, לא צריך להפעיל את הפקודה הזו ב-codelab הזה, אבל כדאי לשמור אותה כהפניה למודרניזציה של האפליקציות שלכם.

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

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

3a83ae745121d70.png

הבדל אחד שתבחינו בו בהשוואה למודול 20 הוא שלחיצה על Login (כניסה) מובילה לחלון קופץ במקום להפניה אוטומטית, כפי שניתן לראות בחלק מצילומי המסך שלמטה. עם זאת, כמו במודול 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 יש מכסת שימוש בחינם, ולכן כל עוד לא חורגים מרמת השימוש הזו, לא אמורים לחייב אתכם. החישוב הזה מתייחס ל-Compute, אבל יכול להיות שיהיו גם חיובים על שירותים רלוונטיים של App Engine. לכן, כדאי לעיין בדף התמחור שלו כדי לקבל מידע נוסף. אם ההעברה הזו כוללת שירותי ענן אחרים, הם יחויבו בנפרד. בכל מקרה, אם רלוונטי, כדאי לעיין בקטע 'ספציפי ל-codelab הזה' שבהמשך.

חשוב לדעת: פריסה בפלטפורמת מחשוב ללא שרת של Google Cloud, כמו App Engine, כרוכה בעלויות קלות של בנייה ואחסון. ל-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 Datastore מסופק על ידי Cloud Datastore (Cloud Firestore במצב Datastore), שגם לו יש רמת שירות בחינם. מידע נוסף זמין במחירון שלו.
  • השימוש בפלטפורמת Cloud Identity הוא בחינם ברמה מסוימת, בהתאם לשירותים שבהם אתם משתמשים. פרטים נוספים מופיעים בדף התמחור.
  • השימוש ב-Cloud Resource Manager API הוא בחינם ברובו, בהתאם לדף התמחור שלו.

השלבים הבאים

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

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

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

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

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

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

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

בעיות או משוב לגבי Codelabs

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

מקורות מידע על העברת נתונים

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

Codelab

Python 2

Python 3

מודול 20

קוד

(n/a)

יחידה 21 (ה-Codelab הזה)

קוד

קוד

מקורות אונליין

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

פלטפורמת Cloud Identity ו-Cloud Marketplace

Cloud Resource Manager, ‏ Cloud IAM, ‏ SDK של Firebase לאדמינים

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

מקורות מידע נוספים על מודול ההעברה

העברה של App Engine

פלטפורמת App Engine

Cloud SDK

מידע אחר על Cloud

סרטונים

רישיון

עבודה זו מורשית תחת רישיון Creative Commons שמותנה בייחוס 2.0 כללי.