|
| 1 | +package hooks |
| 2 | + |
| 3 | +import ( |
| 4 | + "encoding/json" |
| 5 | + "errors" |
| 6 | + "fmt" |
| 7 | + "github.com/cloudfoundry/libbuildpack" |
| 8 | + "os" |
| 9 | + "path/filepath" |
| 10 | + "strings" |
| 11 | +) |
| 12 | + |
| 13 | +type SealightsPlan struct { |
| 14 | + Credentials SealightsCredentials `json:"credentials"` |
| 15 | + Name string `json:"name,omitempty"` |
| 16 | +} |
| 17 | +type SealightsCredentials struct { |
| 18 | + Token string `json:"token",omitempty` |
| 19 | + TokenFile string `json:"tokenFile",omitempty` |
| 20 | + BsId string `json:"bsid",omitempty` |
| 21 | + BsIdFile string `json:"bsidFile",omitempty` |
| 22 | + Proxy string `json:"proxy",omitempty` |
| 23 | + LabId string `json:"labid",omitempty` |
| 24 | + Version string `json:"version",omitempty` |
| 25 | +} |
| 26 | +type SealightsConfig struct { |
| 27 | + Token string |
| 28 | + TokenFile string |
| 29 | + BsId string |
| 30 | + BsIdFile string |
| 31 | + Proxy string |
| 32 | + LabId string |
| 33 | + Version string |
| 34 | +} |
| 35 | + |
| 36 | +func NewSealightsConfig() *SealightsConfig { |
| 37 | + return &SealightsConfig{ |
| 38 | + Token: "", |
| 39 | + TokenFile: "", |
| 40 | + BsId: "", |
| 41 | + BsIdFile: "", |
| 42 | + Proxy: "", |
| 43 | + LabId: "", |
| 44 | + } |
| 45 | +} |
| 46 | + |
| 47 | +func (sc *SealightsConfig) getEnv(key, fallback string) string { |
| 48 | + if value, ok := os.LookupEnv(key); ok && value != "" { |
| 49 | + return value |
| 50 | + } |
| 51 | + return fallback |
| 52 | +} |
| 53 | +func (sc *SealightsConfig) parseSealightsPlan(plan SealightsPlan) *SealightsConfig { |
| 54 | + sc.Token = sc.getEnv("SL_TOKEN", plan.Credentials.Token) |
| 55 | + sc.TokenFile = sc.getEnv("SL_TOKEN_FILE", plan.Credentials.TokenFile) |
| 56 | + sc.BsId = sc.getEnv("SL_BUILD_SESSION_ID", plan.Credentials.BsId) |
| 57 | + sc.BsIdFile = sc.getEnv("SL_BUILD_SESSION_ID_FILE", plan.Credentials.BsIdFile) |
| 58 | + sc.Proxy = sc.getEnv("SL_PROXY", plan.Credentials.Proxy) |
| 59 | + sc.LabId = sc.getEnv("SL_LAB_ID", plan.Credentials.LabId) |
| 60 | + return sc |
| 61 | +} |
| 62 | + |
| 63 | +func (sc *SealightsConfig) GetStartFlags() string { |
| 64 | + var flags []string |
| 65 | + if sc.Token != "" { |
| 66 | + flags = append(flags, fmt.Sprintf("--token %s", sc.Token)) |
| 67 | + } else if sc.TokenFile != "" { |
| 68 | + flags = append(flags, fmt.Sprintf("--tokenfile %s", sc.TokenFile)) |
| 69 | + } |
| 70 | + if sc.BsId != "" { |
| 71 | + flags = append(flags, fmt.Sprintf("--buildsessionid %s", sc.BsId)) |
| 72 | + } else if sc.BsIdFile != "" { |
| 73 | + flags = append(flags, fmt.Sprintf("--buildsessionidfile %s", sc.BsIdFile)) |
| 74 | + } |
| 75 | + if sc.Proxy != "" { |
| 76 | + flags = append(flags, fmt.Sprintf("--proxy %s", sc.Proxy)) |
| 77 | + } |
| 78 | + if sc.LabId != "" { |
| 79 | + flags = append(flags, fmt.Sprintf("--labid %s", sc.LabId)) |
| 80 | + } |
| 81 | + if len(flags) == 0 { |
| 82 | + return "" |
| 83 | + } |
| 84 | + return " " + strings.Join(flags, " ") |
| 85 | +} |
| 86 | + |
| 87 | +type SealightsHook struct { |
| 88 | + libbuildpack.DefaultHook |
| 89 | + Log *libbuildpack.Logger |
| 90 | +} |
| 91 | + |
| 92 | +func (sh SealightsHook) BeforeCompile(stager *libbuildpack.Stager) error { |
| 93 | + vcapServices := os.Getenv("VCAP_SERVICES") |
| 94 | + services := make(map[string][]SealightsPlan) |
| 95 | + |
| 96 | + err := json.Unmarshal([]byte(vcapServices), &services) |
| 97 | + if err != nil { |
| 98 | + sh.Log.Debug("Could not unmarshall VCAP_SERVICES JSON exiting: %v", err) |
| 99 | + return nil |
| 100 | + } |
| 101 | + |
| 102 | + sealgithsServiceName, sealightsPlan := getSealightsServiceName(services, sh.Log) |
| 103 | + |
| 104 | + if sealgithsServiceName == "" { |
| 105 | + sh.Log.Debug("No Sealights service found, exiting") |
| 106 | + return nil |
| 107 | + } |
| 108 | + sh.Log.BeginStep("Setting up Sealights hook") |
| 109 | + sealightsConfig := NewSealightsConfig().parseSealightsPlan(sealightsPlan) |
| 110 | + if err := sh.RewriteRequirementsFile(stager, sealightsConfig.Version); err != nil { |
| 111 | + sh.Log.Error("Could not write requirements file with Sealights package: %v", err) |
| 112 | + return err |
| 113 | + } |
| 114 | + |
| 115 | + if err := sh.RewriteProcFileWithSealgiths(stager, sealightsConfig.GetStartFlags()); err != nil { |
| 116 | + sh.Log.Error("Failed to rewrite Procfile with Sealights: %s", err.Error()) |
| 117 | + return fmt.Errorf("Failed to rewrite Procfile with Sealights: %s", err.Error()) |
| 118 | + } |
| 119 | + |
| 120 | + sh.Log.Info("Successfully set up Sealights hook") |
| 121 | + return nil |
| 122 | +} |
| 123 | +func (sh SealightsHook) RewriteProcFileWithSealgiths(stager *libbuildpack.Stager, cfgFlags string) error { |
| 124 | + sh.Log.BeginStep("Rewriting ProcFile to start with Sealights") |
| 125 | + |
| 126 | + file := filepath.Join(stager.BuildDir(), "Procfile") |
| 127 | + |
| 128 | + if exists, _ := libbuildpack.FileExists(file); exists { |
| 129 | + if err := sh.RewriteProcFile(file, cfgFlags); err != nil { |
| 130 | + return err |
| 131 | + } |
| 132 | + fileContents, _ := os.ReadFile(file) |
| 133 | + sh.Log.Info(string(fileContents)) |
| 134 | + } else { |
| 135 | + sh.Log.Info("Cannot find Procfile, skipping this step!") |
| 136 | + } |
| 137 | + return nil |
| 138 | +} |
| 139 | +func getSealightsServiceName(services map[string][]SealightsPlan, log *libbuildpack.Logger) (string, SealightsPlan) { |
| 140 | + for serviceName, servicePlans := range services { |
| 141 | + if strings.Contains(serviceName, "sealights") { |
| 142 | + return serviceName, servicePlans[0] |
| 143 | + } |
| 144 | + } |
| 145 | + |
| 146 | + // checking if there is a user-provided service with name sealights |
| 147 | + userProvidedServices, keyExists := services["user-provided"] |
| 148 | + if !keyExists { |
| 149 | + return "", SealightsPlan{} |
| 150 | + } |
| 151 | + |
| 152 | + for _, plan := range userProvidedServices { |
| 153 | + if strings.Contains(plan.Name, "sealights") { |
| 154 | + return plan.Name, plan |
| 155 | + } |
| 156 | + } |
| 157 | + return "", SealightsPlan{} |
| 158 | +} |
| 159 | + |
| 160 | +func (sh SealightsHook) RewriteProcFile(procFilePath string, cfgFlags string) error { |
| 161 | + startCommand, err := os.ReadFile(procFilePath) |
| 162 | + if err != nil { |
| 163 | + return fmt.Errorf("Error reading file %s: %v", procFilePath, err) |
| 164 | + } |
| 165 | + newCommand, err := sh.GenerateStartUpCommand(string(startCommand), cfgFlags) |
| 166 | + if err != nil { |
| 167 | + return err |
| 168 | + } |
| 169 | + |
| 170 | + if err := os.WriteFile(procFilePath, []byte(newCommand), 0666); err != nil { |
| 171 | + return fmt.Errorf("Error writing file %s: %v", procFilePath, err) |
| 172 | + } |
| 173 | + return nil |
| 174 | +} |
| 175 | + |
| 176 | +func (sh SealightsHook) GenerateStartUpCommand(startCommand string, cfgFlags string) (string, error) { |
| 177 | + webCommands := strings.SplitN(startCommand, ":", 2) |
| 178 | + if len(webCommands) != 2 { |
| 179 | + return "", errors.New("improper format found in Procfile") |
| 180 | + } |
| 181 | + return fmt.Sprintf("web: sl-python run%s -- %s", cfgFlags, webCommands[1]), nil |
| 182 | +} |
| 183 | +func (sh SealightsHook) RewriteRequirementsFile(stager *libbuildpack.Stager, version string) error { |
| 184 | + sealightsPackage := "sealights-python-agent" |
| 185 | + if version != "" { |
| 186 | + sealightsPackage = fmt.Sprintf("sealights-python-agent==%s", version) |
| 187 | + } |
| 188 | + reqFile := filepath.Join(stager.BuildDir(), "requirements.txt") |
| 189 | + writeFlag := os.O_APPEND | os.O_WRONLY |
| 190 | + packageName := "\n" + sealightsPackage |
| 191 | + |
| 192 | + if exists, err := libbuildpack.FileExists(reqFile); err != nil { |
| 193 | + return err |
| 194 | + } else if !exists { |
| 195 | + sh.Log.Info("Requirements file not found creating one with sealights packages") |
| 196 | + writeFlag = os.O_CREATE | os.O_WRONLY |
| 197 | + packageName = sealightsPackage |
| 198 | + } |
| 199 | + f, err := os.OpenFile(reqFile, writeFlag, 0666) |
| 200 | + if err != nil { |
| 201 | + return err |
| 202 | + } |
| 203 | + defer func() { |
| 204 | + _ = f.Close() |
| 205 | + }() |
| 206 | + if _, err = f.WriteString(packageName); err != nil { |
| 207 | + return err |
| 208 | + } |
| 209 | + return nil |
| 210 | +} |
| 211 | + |
| 212 | +func init() { |
| 213 | + logger := libbuildpack.NewLogger(os.Stdout) |
| 214 | + libbuildpack.AddHook(&SealightsHook{ |
| 215 | + Log: logger, |
| 216 | + }) |
| 217 | +} |
0 commit comments