images/controller/cmd/app_finder/app_finder.go (158 lines of code) (raw):

/* Copyright 2019 Google Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package main import ( "bufio" "fmt" "io/ioutil" "log" "os" "path" "path/filepath" "regexp" "strings" "time" broker "selkies.io/controller/pkg" ) func main() { log.Printf("Starting broker app discovery service") // Set from downward API. namespace := os.Getenv("NAMESPACE") if len(namespace) == 0 { log.Fatal("Missing NAMESPACE env.") } // Allow for single run singleIteration := false if os.Getenv("SINGLE_ITERATION") == "true" { singleIteration = true } // Map of cached app manifest checksums bundleManifestChecksums := make(map[string]string, 0) userBundleManifestChecksums := make(map[string]string, 0) for { // Fetch all user app configs appConfigs, err := broker.FetchBrokerAppConfigs(namespace) if err != nil { log.Printf("failed to fetch broker app configs: %v", err) } // List of app specs that will be added to the registered app manifest. registeredApps := broker.NewRegisteredAppManifest() // Fetch all configmaps in single call nsConfigMaps, err := broker.GetConfigMaps(namespace) if err != nil { log.Printf("failed to fetch broker appconfig map bundles: %v", err) } // Fetch data required for Egress Network Policy networkPolicyData, err := broker.GetEgressNetworkPolicyData(namespace) if err != nil { log.Printf("failed to fetch networkpolicy data: %v", err) } registeredApps.NetworkPolicyData = networkPolicyData // Base dir for temp directory where updated files are staged. tmpDirBase := path.Dir(broker.BundleSourceBaseDir) for _, appConfig := range appConfigs { bundleCMName := appConfig.Spec.Bundle.ConfigMapRef.Name authzCMName := appConfig.Spec.Authorization.ConfigMapRef.Name appName := appConfig.Metadata.Name bundleDestDir := path.Join(broker.BundleSourceBaseDir, appName) userBundleDestDir := path.Join(broker.UserBundleSourceBaseDir, appName) foundUserBundleCount := 0 // Find and save configmap data for required bundle and optional authz foundBundle := false foundAllUserBundles := false foundAuthzCM := false for _, cm := range nsConfigMaps { // Match on bundle configmap name if cm.Metadata.Name == bundleCMName { // Update working mainfests if bundle has changed. cacheKey := fmt.Sprintf("%s-%s", appName, cm.Metadata.Name) if err := copyConfigMapDataIfChanged(cm, tmpDirBase, bundleDestDir, cacheKey, bundleManifestChecksums); err != nil { log.Printf("%v", err) } else { foundBundle = true } } // Match on a user bundle configmap name for i, userBundle := range appConfig.Spec.UserBundles { userBundleCMName := userBundle.ConfigMapRef.Name // Match on user bundle configmap name if cm.Metadata.Name == userBundleCMName { destDir := path.Join(userBundleDestDir, fmt.Sprintf("%d", i)) cacheKey := fmt.Sprintf("%s-%s", appName, cm.Metadata.Name) if err := copyConfigMapDataIfChanged(cm, tmpDirBase, destDir, cacheKey, userBundleManifestChecksums); err != nil { log.Printf("%v", err) } else { foundUserBundleCount++ } } } // Match on authz configmap name if cm.Metadata.Name == authzCMName { foundAuthzCM = true // Extract authorization members and append them to the appConfig.Spec.AuthorizedUsers array. for _, data := range cm.Data { scanner := bufio.NewScanner(strings.NewReader(data)) for scanner.Scan() { userPat := scanner.Text() // Skip comment lines. if !strings.HasPrefix(userPat, "#") { _, err := regexp.Compile(userPat) if err != nil { log.Printf("WARN: invalid authorized user pattern found in ConfigMap %s: '%s', skipped.", authzCMName, userPat) } else { appConfig.Spec.AuthorizedUsers = append(appConfig.Spec.AuthorizedUsers, userPat) } } } } } } if foundUserBundleCount == len(appConfig.Spec.UserBundles) { foundAllUserBundles = true } if !foundBundle { log.Printf("Bundle manifests ConfigMap %s not found for app %s", bundleCMName, appName) } else if len(appConfig.Spec.UserBundles) > 0 && !foundAllUserBundles { log.Printf("Failed to find all spec.userBundles for app %s", appName) } else { if len(authzCMName) > 0 && !foundAuthzCM { log.Printf("Failed to find authorization ConfigMap bundle %s for app %s", authzCMName, appName) } // App is valid and bundle is ready, add to registered apps. if !appConfig.Spec.Disabled { registeredApps.Add(appConfig.Spec) } } } // Prune build source directories foundDirs, err := filepath.Glob(path.Join(broker.BundleSourceBaseDir, "*")) if err != nil { log.Printf("failed to list app directories to prune: %v", err) } for _, dirName := range foundDirs { found := false for _, appConfig := range appConfigs { if appConfig.Metadata.Name == path.Base(dirName) { found = true break } } if !found { log.Printf("removing build source: %s", dirName) os.RemoveAll(dirName) } } // Write registered app manifest if err := registeredApps.WriteJSON(broker.RegisteredAppsManifestJSONFile); err != nil { log.Printf("failed to write registered app manifest: %s: %v", broker.RegisteredAppsManifestJSONFile, err) } if singleIteration { break } time.Sleep(5 * time.Second) } } // Helper function to only update the working manifest if the configmap content has changed. func copyConfigMapDataIfChanged(cm broker.ConfigMapObject, tmpDirBase, destDir, cacheKey string, checksums map[string]string) error { tmpDir, err := ioutil.TempDir(tmpDirBase, "bundle") if err != nil { return fmt.Errorf("failed to create staging dir at base: %s: %v", tmpDirBase, err) } defer os.RemoveAll(tmpDir) if err := cm.SaveDataToDirectory(tmpDir); err != nil { return fmt.Errorf("failed to save bundle ConfigMap '%s' to %s: %v", cm.Metadata.Name, tmpDir, err) } // Compute and cache checksum to know if we need to update the working mainfests. prevChecksum := checksums[cacheKey] if checksums[cacheKey], err = broker.ChecksumDeploy(tmpDir); err != nil { return fmt.Errorf("failed to checksum build output directory: %v", err) } if prevChecksum != checksums[cacheKey] { log.Printf("%s manifest checksum: %s", cacheKey, checksums[cacheKey]) if err := os.MkdirAll(path.Dir(destDir), os.ModePerm); err != nil { return err } os.RemoveAll(destDir) if err := os.Rename(tmpDir, destDir); err != nil { return fmt.Errorf("failed to move manifest bundle ConfigMap data to %s: %v", destDir, err) } } return nil }