github.com/apache/beam/sdks/v2@v2.48.2/typescript/container/boot.go (about)

     1  // Licensed to the Apache Software Foundation (ASF) under one or more
     2  // contributor license agreements.  See the NOTICE file distributed with
     3  // this work for additional information regarding copyright ownership.
     4  // The ASF licenses this file to You under the Apache License, Version 2.0
     5  // (the "License"); you may not use this file except in compliance with
     6  // the License.  You may obtain a copy of the License at
     7  //
     8  //    http://www.apache.org/licenses/LICENSE-2.0
     9  //
    10  // Unless required by applicable law or agreed to in writing, software
    11  // distributed under the License is distributed on an "AS IS" BASIS,
    12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  // See the License for the specific language governing permissions and
    14  // limitations under the License.
    15  
    16  package main
    17  
    18  import (
    19  	"context"
    20  	"flag"
    21  	"fmt"
    22  	"log"
    23  	"os"
    24  	"path/filepath"
    25  	"strings"
    26  	"sync"
    27  
    28  	"github.com/apache/beam/sdks/v2/go/container/tools"
    29  	"github.com/apache/beam/sdks/v2/go/pkg/beam/artifact"
    30  	"github.com/apache/beam/sdks/v2/go/pkg/beam/util/execx"
    31  	"github.com/apache/beam/sdks/v2/go/pkg/beam/util/grpcx"
    32  )
    33  
    34  var (
    35  	// Contract: https://s.apache.org/beam-fn-api-container-contract.
    36  
    37  	id                = flag.String("id", "", "Local identifier (required).")
    38  	loggingEndpoint   = flag.String("logging_endpoint", "", "Local logging endpoint for FnHarness (required).")
    39  	artifactEndpoint  = flag.String("artifact_endpoint", "", "Local artifact endpoint for FnHarness (required).")
    40  	provisionEndpoint = flag.String("provision_endpoint", "", "Local provision endpoint for FnHarness (required).")
    41  	controlEndpoint   = flag.String("control_endpoint", "", "Local control endpoint for FnHarness (required).")
    42  	semiPersistDir    = flag.String("semi_persist_dir", "/tmp", "Local semi-persistent directory (optional).")
    43  )
    44  
    45  const entrypoint = "apache-beam-worker"
    46  
    47  func main() {
    48  	flag.Parse()
    49  	if *id == "" {
    50  		log.Fatal("No id provided.")
    51  	}
    52  	if *provisionEndpoint == "" {
    53  		log.Fatal("No provision endpoint provided.")
    54  	}
    55  
    56  	ctx := grpcx.WriteWorkerID(context.Background(), *id)
    57  
    58  	info, err := tools.ProvisionInfo(ctx, *provisionEndpoint)
    59  	if err != nil {
    60  		log.Fatalf("Failed to obtain provisioning information: %v", err)
    61  	}
    62  	log.Printf("Provision info:\n%v", info)
    63  
    64  	// TODO(BEAM-8201): Simplify once flags are no longer used.
    65  	if info.GetLoggingEndpoint().GetUrl() != "" {
    66  		*loggingEndpoint = info.GetLoggingEndpoint().GetUrl()
    67  	}
    68  	if info.GetArtifactEndpoint().GetUrl() != "" {
    69  		*artifactEndpoint = info.GetArtifactEndpoint().GetUrl()
    70  	}
    71  	if info.GetControlEndpoint().GetUrl() != "" {
    72  		*controlEndpoint = info.GetControlEndpoint().GetUrl()
    73  	}
    74  
    75  	if *loggingEndpoint == "" {
    76  		log.Fatal("No logging endpoint provided.")
    77  	}
    78  	if *artifactEndpoint == "" {
    79  		log.Fatal("No artifact endpoint provided.")
    80  	}
    81  	if *controlEndpoint == "" {
    82  		log.Fatal("No control endpoint provided.")
    83  	}
    84  	logger := &tools.Logger{Endpoint: *loggingEndpoint}
    85  	logger.Printf(ctx, "Initializing typescript harness: %v", strings.Join(os.Args, " "))
    86  
    87  	// (1) Obtain the pipeline options
    88  
    89  	options, err := tools.ProtoToJSON(info.GetPipelineOptions())
    90  	if err != nil {
    91  		logger.Fatalf(ctx, "Failed to convert pipeline options: %v", err)
    92  	}
    93  
    94  	// (2) Retrieve and install the staged packages.
    95  
    96  	dir := filepath.Join(*semiPersistDir, *id, "staged")
    97  	artifacts, err := artifact.Materialize(ctx, *artifactEndpoint, info.GetDependencies(), info.GetRetrievalToken(), dir)
    98  	if err != nil {
    99  		logger.Fatalf(ctx, "Failed to retrieve staged files: %v", err)
   100  	}
   101  
   102  	// Create a package.json that names given dependencies as overrides.
   103  	npmOverrides := make(map[string]string)
   104  	for _, v := range artifacts {
   105  		name, _ := artifact.MustExtractFilePayload(v)
   106  		path := filepath.Join(dir, name)
   107  		if v.RoleUrn == "beam:artifact:type:npm_dep:v1" {
   108  			// Npm cannot handle arbitrary suffixes.
   109  			suffixedPath := path + ".tar"
   110  			if err := os.Rename(path, suffixedPath); err != nil {
   111  				logger.Fatalf(ctx, "unable to rename %v to %v: %v", path, suffixedPath, err)
   112  			}
   113  			npmOverrides[string(v.RolePayload)] = suffixedPath
   114  		}
   115  	}
   116  	if len(npmOverrides) > 0 {
   117  		f, err := os.Create("package.json")
   118  		if err != nil {
   119  			logger.Fatalf(ctx, "unable to os.Create(%q): %v", "package.json", err)
   120  		}
   121  		defer f.Close()
   122  		f.WriteString("{\n")
   123  		f.WriteString("  \"name\": \"beam-worker\",\n  \"version\": \"1.0.0\",\n")
   124  		f.WriteString("  \"overrides\": {\n")
   125  		needsComma := false
   126  		for pkg, path := range npmOverrides {
   127  			if needsComma {
   128  				f.WriteString(",")
   129  			} else {
   130  				needsComma = true
   131  			}
   132  			f.WriteString(fmt.Sprintf("    %q: %q\n", pkg, "file:"+path))
   133  		}
   134  		f.WriteString("  }\n")
   135  		f.WriteString("}\n")
   136  	}
   137  	execx.Execute("cat", "package.json")
   138  
   139  	// Now install any other npm packages.
   140  	for _, v := range artifacts {
   141  		name, _ := artifact.MustExtractFilePayload(v)
   142  		path := filepath.Join(dir, name)
   143  		if v.RoleUrn == "beam:artifact:type:npm:v1" {
   144  			// Npm cannot handle arbitrary suffixes.
   145  			suffixedPath := path + ".tar"
   146  			if err := os.Rename(path, suffixedPath); err != nil {
   147  				logger.Fatalf(ctx, "unable to rename %v to %v: %v", path, suffixedPath, err)
   148  			}
   149  			if err := execx.Execute("npm", "install", suffixedPath); err != nil {
   150  				logger.Fatalf(ctx, "Error installing package %q: %v", suffixedPath, err)
   151  			}
   152  		}
   153  	}
   154  
   155  	// (3) Invoke the Node entrypoint, passing the Fn API container contract info as flags.
   156  
   157  	args := []string{
   158  		entrypoint,
   159  		"--logging_endpoint=" + *loggingEndpoint,
   160  		"--control_endpoint=" + *controlEndpoint,
   161  		"--semi_persist_dir=" + *semiPersistDir,
   162  		"--options=" + options,
   163  	}
   164  
   165  	if info.GetStatusEndpoint() != nil {
   166  		args = append(args, "--status_endpoint="+info.GetStatusEndpoint().GetUrl())
   167  	}
   168  
   169  	workerIds := append([]string{*id}, info.GetSiblingWorkerIds()...)
   170  	var wg sync.WaitGroup
   171  	wg.Add(len(workerIds))
   172  	for _, workerId := range workerIds {
   173  		go func(workerId string) {
   174  			workerArgs := append(append([]string{}, args...), "--id="+workerId)
   175  			logger.Printf(ctx, "Executing: npx %v", strings.Join(workerArgs, " "))
   176  			logger.Fatalf(ctx, "User program exited: %v", execx.Execute("npx", workerArgs...))
   177  		}(workerId)
   178  	}
   179  	wg.Wait()
   180  }