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 }