【问题标题】:Create Google Cloud Function using API in Python在 Python 中使用 API 创建 Google Cloud Function
【发布时间】:2018-04-30 19:03:54
【问题描述】:

我正在使用 Python(3.6) 和 Django(1.10) 开展一个项目,其中我需要使用 API 请求在 Google 云中创建一个函数。

如何在创建该函数时以 zip 存档的形式上传代码?

这是我尝试过的:

来自views.py:

    def post(self, request, *args, **kwargs):
    if request.method == 'POST':
        post_data = request.POST.copy()
        post_data.update({'user': request.user.pk})
        form = forms.SlsForm(post_data, request.FILES)
        print('get post request')
        if form.is_valid():
            func_obj = form
            func_obj.user = request.user
            func_obj.project = form.cleaned_data['project']
            func_obj.fname = form.cleaned_data['fname']
            func_obj.fmemory = form.cleaned_data['fmemory']
            func_obj.entryPoint = form.cleaned_data['entryPoint']
            func_obj.sourceFile = form.cleaned_data['sourceFile']
            func_obj.sc_github = form.cleaned_data['sc_github']
            func_obj.sc_inline_index = form.cleaned_data['sc_inline_index']
            func_obj.sc_inline_package = form.cleaned_data['sc_inline_package']
            func_obj.bucket = form.cleaned_data['bucket']
            func_obj.save()
            service = discovery.build('cloudfunctions', 'v1', http=views.getauth(), cache_discovery=False)
            requ = service.projects().locations().functions().generateUploadUrl(parent='projects/' + func_obj.project + '/locations/us-central1', body={})
            resp = requ.execute()
            print(resp)
            try:
                auth = views.getauth()
                # Prepare Request Body
                req_body = {
                    "CloudFunction": {
                        "name": func_obj.fname,
                        "entryPoint": func_obj.entryPoint,
                        "timeout": '60s',
                        "availableMemoryMb": func_obj.fmemory,
                        "sourceArchiveUrl": func_obj.sc_github,
                    },
                    "sourceUploadUrl": func_obj.bucket,
                }
                service = discovery.build('cloudfunctions', 'v1beta2', http=auth, cachce_dicovery=False)
                func_req = service.projects().locations().functions().create(location='projects/' + func_obj.project
                                                                                      + '/locations/-',
                                                                             body=req_body)
                func_res = func_req.execute()
                print(func_res)
                return HttpResponse('Submitted',)
            except:
                return HttpResponse(status=500)

        return HttpResponse('Sent!')

以下更新代码:

            if form.is_valid():
            func_obj = form
            func_obj.user = request.user
            func_obj.project = form.cleaned_data['project']
            func_obj.fname = form.cleaned_data['fname']
            func_obj.fmemory = form.cleaned_data['fmemory']
            func_obj.entryPoint = form.cleaned_data['entryPoint']
            func_obj.sourceFile = form.cleaned_data['sourceFile']
            func_obj.sc_github = form.cleaned_data['sc_github']
            func_obj.sc_inline_index = form.cleaned_data['sc_inline_index']
            func_obj.sc_inline_package = form.cleaned_data['sc_inline_package']
            func_obj.bucket = form.cleaned_data['bucket']
            func_obj.save()

            #######################################################################
            # FIRST APPROACH FOR FUNCTION CREATION USING STORAGE BUCKET
            #######################################################################

            file_name = os.path.join(IGui.settings.BASE_DIR, 'media/archives/', func_obj.sourceFile.name)
            print(file_name)

            service = discovery.build('cloudfunctions', 'v1')
            func_api = service.projects().locations().functions()
            url_svc_req = func_api.generateUploadUrl(parent='projects/'
                                                            + func_obj.project
                                                            + '/locations/us-central1',
                                                     body={})
            url_svc_res = url_svc_req.execute()
            print(url_svc_res)

            upload_url = url_svc_res['uploadUrl']
            print(upload_url)
            headers = {
                'content-type': 'application/zip',
                'x-goog-content-length-range': '0,104857600'
            }
            print(requests.put(upload_url, headers=headers, data=func_obj.sourceFile.name))
            auth = views.getauth()
            # Prepare Request Body
            name = "projects/{}/locations/us-central1/functions/{}".format(func_obj.project, func_obj.fname,)
            print(name)
            req_body = {
              "name": name,
              "entryPoint": func_obj.entryPoint,
              "timeout": "3.5s",
              "availableMemoryMb": func_obj.fmemory,
              "sourceUploadUrl": upload_url,
              "httpsTrigger": {},
            }
            service = discovery.build('cloudfunctions', 'v1')
            func_api = service.projects().locations().functions()

            response = func_api.create(location='projects/' + func_obj.project + '/locations/us-central1',
                                                body=req_body).execute()

            pprint.pprint(response)

现在函数已经创建成功,但是由于源代码没有上传到存储桶而失败,这可能是问题所在:

upload_url = url_svc_res['uploadUrl']
            print(upload_url)
            headers = {
                'content-type': 'application/zip',
                'x-goog-content-length-range': '0,104857600'
            }
            print(requests.put(upload_url, headers=headers, data=func_obj.sourceFile.name))

【问题讨论】:

    标签: python google-cloud-platform google-cloud-storage


    【解决方案1】:

    在请求正文中,请求中有一个字典“CloudFunction”。 “CloudFunction”的内容应该直接在请求中。

    request_body = {
        "name": parent + '/functions/' + name,
        "entryPoint": entry_point,
        "sourceUploadUrl": upload_url,
        "httpsTrigger": {}
    }
    

    我建议使用"Try this API" 来发现projects.locations.functions.create 的结构。

    "sourceArchiveUrl""sourceUploadUrl" 不能同时出现。这在Resorce Cloud Function 中有解释:

    // Union field source_code can be only one of the following:
    "sourceArchiveUrl": string,
    "sourceRepository": { object(SourceRepository) },
    "sourceUploadUrl": string,
    // End of list of possible types for union field source_code.
    

    在其余答案中,我假设您想使用"sourceUploadUrl"。它要求您向它传递一个由.generateUploadUrl(...).execute() 返回给您的 URL。见documentation:

    sourceUploadUrl -> 字符串

    用于源上传的 Google Cloud Storage 签名 URL, 由 [google.cloud.functions.v1.GenerateUploadUrl][]

    生成

    但在传递之前,您需要将 zip 文件上传到此 URL:

    curl -X PUT "${URL}" -H 'content-type:application/zip' -H 'x-goog-content-length-range: 0,104857600'  -T test.zip
    

    或者在python中:

        headers = {
            'content-type':'application/zip',
            'x-goog-content-length-range':'0,104857600'
        }
        print(requests.put(upload_url, headers=headers, data=data))
    

    这是最棘手的部分:

    • 大小写很重要,应该是小写。因为签名是根据哈希计算的 (here)

    • 您需要 'content-type':'application/zip'。我从逻辑上推断出这一点,因为文档没有提到它。 (here)

    • x-goog-content-length-range: min,max 是所有 PUT 云存储请求的强制性要求,在这种情况下是隐式假设的。更多内容here

    • 104857600,上一个条目中的最大值,是一个神奇的数字,我在任何地方都没有找到。

    data 是一个 FileLikeObject。

    我还假设您想使用httpsTrigger。对于云功能,您只能选择一个触发字段。 Here 据说触发器是一个联合字段。但是,对于 httpsTrigger,您可以将其保留为空字典,因为其内容不会影响结果。目前为止。

    request_body = {
        "name": parent + '/functions/' + name,
        "entryPoint": entry_point,
        "sourceUploadUrl": upload_url,
        "httpsTrigger": {}
    }
    

    对于.create(),您可以安全地使用“v1”而不是“v1beta2”。

    这是一个完整的工作示例。如果我将它作为代码的一部分提供给您,那会很复杂,但是您可以轻松地集成它。

    import pprint
    import zipfile
    import requests
    from tempfile import TemporaryFile
    from googleapiclient import discovery
    
    project_id = 'your_project_id'
    region = 'us-central1'
    parent = 'projects/{}/locations/{}'.format(project_id, region)
    print(parent)
    name = 'ExampleFunctionFibonacci'
    entry_point = "fibonacci"
    
    service = discovery.build('cloudfunctions', 'v1')
    CloudFunctionsAPI = service.projects().locations().functions()
    upload_url = CloudFunctionsAPI.generateUploadUrl(parent=parent, body={}).execute()['uploadUrl']
    print(upload_url)
    
    
    payload = """/**
     * Responds to any HTTP request that can provide a "message" field in the body.
     *
     * @param {Object} req Cloud Function request context.
     * @param {Object} res Cloud Function response context.
     */
    exports.""" + entry_point + """= function """ + entry_point + """ (req, res) {
      if (req.body.message === undefined) {
        // This is an error case, as "message" is required
        res.status(400).send('No message defined!');
      } else {
        // Everything is ok
        console.log(req.body.message);
        res.status(200).end();
      }
    };"""
    
    
    with TemporaryFile() as data:
        with zipfile.ZipFile(data, 'w', zipfile.ZIP_DEFLATED) as archive:
            archive.writestr('function.js', payload)
    
        data.seek(0)
        headers = {
            'content-type':'application/zip',
            'x-goog-content-length-range':'0,104857600'
        }
        print(requests.put(upload_url, headers=headers, data=data))
    
    # Prepare Request Body
    # https://cloud.google.com/functions/docs/reference/rest/v1/projects.locations.functions#resource-cloudfunction
    
    request_body = {
        "name": parent + '/functions/' + name,
        "entryPoint": entry_point,
        "sourceUploadUrl": upload_url,
        "httpsTrigger": {},
        "runtime": 'nodejs8'
    }
    
    print('https://{}-{}.cloudfunctions.net/{}'.format(region,project_id,name))
    response = CloudFunctionsAPI.create(location=parent, body=request_body).execute()
    
    pprint.pprint(response)
    

    打开并上传一个 zip 文件,如下所示:

    file_name = os.path.join(IGui.settings.BASE_DIR, 'media/archives/', func_obj.sourceFile.name)
    headers = {
        'content-type': 'application/zip',
        'x-goog-content-length-range': '0,104857600'
    }
    
    with open(file_name, 'rb') as data:
        print(requests.put(upload_url, headers=headers, data=data))
    

    【讨论】:

    • 嗨,@A.Queue!首先感谢您的回答。我已经更新了我的代码,请参阅问题!但现在它返回为:File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/ssl.py", line 683, in do_handshake self._sslobj.do_handshake() ssl.SSLEOFError: EOF occurred in violation of protocol (_ssl.c:749) [28/Nov/2017 13:33:10] "POST /user/deploy/serverless/deployment/ HTTP/1.1" 500 17239,甚至文件也没有上传到存储桶上,而是返回 200 状态码。
    • 现在函数创建工作正常但源代码没有上传到存储桶!
    • 这是整个回溯吗?好像是多米诺骨牌效应。到目前为止,您拥有file_name = os.path.join(IGui.settings.BASE_DIR, 'media/archives/', func_obj.sourceFile.name),但您从未使用过它。你也做requests.put(upload_url, headers=headers, data=func_obj.sourceFile.name),但我不确定 func_obj.sourceFile.name 是返回 FileLikeObject 还是带有名称的字符串。我还建议将此代码完全隔离到不同的函数中,以便您可以将其与 django 的其余部分隔离。这个错误很可能是由另一个错误引起的。
    • 这是上传文件的路径:file_name = os.path.join(IGui.settings.BASE_DIR, 'media/archives/', func_obj.sourceFile.name)
    • 要返回 FileLikeObject,您需要像我的代码中那样进行 with open(file_name, 'r') as tmp 构造。然后将其传递给data=tmp 之类的数据。
    猜你喜欢
    • 1970-01-01
    • 2021-07-19
    • 2021-06-05
    • 1970-01-01
    • 1970-01-01
    • 2021-03-25
    • 2022-08-19
    • 2017-02-09
    相关资源
    最近更新 更多