infrastructure/cdn-in-a-box/enroller/enroller.go (1,075 lines of code) (raw):

package main // Licensed to the Apache Software Foundation (ASF) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The ASF licenses this file // to you 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. import ( "encoding/json" "errors" "flag" "fmt" "io" "net/http" "net/url" "os" "path/filepath" "regexp" "strconv" "strings" "time" log "github.com/apache/trafficcontrol/v8/lib/go-log" tc "github.com/apache/trafficcontrol/v8/lib/go-tc" client "github.com/apache/trafficcontrol/v8/traffic_ops/v5-client" "github.com/fsnotify/fsnotify" "github.com/kelseyhightower/envconfig" ) var startedFile = "enroller-started" type session struct { *client.Session } func newSession(reqTimeout time.Duration, toURL string, toUser string, toPass string) (session, error) { s, _, err := client.LoginWithAgent(toURL, toUser, toPass, true, "cdn-in-a-box-enroller", true, reqTimeout) return session{s}, err } func (s session) getParameter(m tc.ParameterV5, header http.Header) (tc.ParameterV5, error) { // TODO: s.GetParameterByxxx() does not seem to work with values with spaces -- // doing this the hard way for now opts := client.RequestOptions{Header: header} parameters, _, err := s.GetParameters(opts) if err != nil { return m, fmt.Errorf("getting Parameters: %v - alerts: %+v", err, parameters.Alerts) } for _, p := range parameters.Response { if p.Name == m.Name && p.Value == m.Value && p.ConfigFile == m.ConfigFile { return p, nil } } return m, fmt.Errorf("no parameter matching name %s, configFile %s, value %s", m.Name, m.ConfigFile, m.Value) } // enrollType takes a json file and creates a Type object using the TO API func enrollType(toSession *session, r io.Reader) error { dec := json.NewDecoder(r) var s tc.TypeV5 err := dec.Decode(&s) if err != nil { log.Infof("error decoding Type: %s", err) return err } alerts, _, err := toSession.CreateType(s, client.RequestOptions{}) if err != nil { for _, alert := range alerts.Alerts { if alert.Level == tc.ErrorLevel.String() && strings.Contains(alert.Text, "already exists") { log.Infof("Type '%s' already exists", s.Name) return nil } } err = fmt.Errorf("error creating Type: %v - alerts: %+v", err, alerts.Alerts) log.Infoln(err) return err } enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") err = enc.Encode(&alerts) return err } // enrollCDN takes a json file and creates a CDN object using the TO API func enrollCDN(toSession *session, r io.Reader) error { dec := json.NewDecoder(r) var s tc.CDNV5 err := dec.Decode(&s) if err != nil { log.Infof("error decoding CDN: %v", err) return err } alerts, _, err := toSession.CreateCDN(s, client.RequestOptions{}) if err != nil { for _, alert := range alerts.Alerts { if strings.Contains(alert.Text, "already exists") { log.Infof("CDN '%s' already exists", s.Name) return nil } } log.Infof("error creating CDN: %v - alerts: %+v", err, alerts.Alerts) return err } enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") err = enc.Encode(&alerts) return err } func enrollASN(toSession *session, r io.Reader) error { dec := json.NewDecoder(r) var s tc.ASNV5 err := dec.Decode(&s) if err != nil { log.Infof("error decoding ASN: %s\n", err) return err } alerts, _, err := toSession.CreateASN(s, client.RequestOptions{}) if err != nil { for _, alert := range alerts.Alerts { if strings.Contains(alert.Text, "already exists") { log.Infof("asn %d already exists", s.ASN) return nil } } err = fmt.Errorf("error creating ASN: %s - alerts: %+v", err, alerts.Alerts) log.Infoln(err) return err } enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") err = enc.Encode(&alerts) return err } // enrollCachegroup takes a json file and creates a Cachegroup object using the TO API func enrollCachegroup(toSession *session, r io.Reader) error { dec := json.NewDecoder(r) var s tc.CacheGroupNullableV5 err := dec.Decode(&s) if err != nil { log.Infof("error decoding Cache Group: '%s'", err) return err } alerts, _, err := toSession.CreateCacheGroup(s, client.RequestOptions{}) if err != nil { for _, alert := range alerts.Alerts.Alerts { if strings.Contains(alert.Text, "already exists") { log.Infof("Cache Group '%s' already exists", *s.Name) return nil } } err = fmt.Errorf("error creating Cache Group: %v - alerts: %+v", err, alerts.Alerts) log.Infoln(err) return err } enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") err = enc.Encode(&alerts) return err } func enrollTopology(toSession *session, r io.Reader) error { dec := json.NewDecoder(r) var s tc.TopologyV5 err := dec.Decode(&s) if err != nil && err != io.EOF { log.Infof("error decoding Topology: %s", err) return err } alerts, _, err := toSession.CreateTopology(s, client.RequestOptions{}) if err != nil { for _, alert := range alerts.Alerts.Alerts { if alert.Level == tc.ErrorLevel.String() && strings.Contains(alert.Text, "already exists") { log.Infof("topology %s already exists", s.Name) return nil } } err = fmt.Errorf("error creating Topology: %v - alerts: %+v", err, alerts.Alerts.Alerts) log.Infoln(err) return err } enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") err = enc.Encode(&alerts) return err } func enrollDeliveryService(toSession *session, r io.Reader) error { dec := json.NewDecoder(r) var s tc.DeliveryServiceV5 err := dec.Decode(&s) if err != nil { log.Infof("error decoding DeliveryService: %v", err) return err } alerts, _, err := toSession.CreateDeliveryService(s, client.RequestOptions{}) if err != nil { for _, alert := range alerts.Alerts.Alerts { if strings.Contains(alert.Text, "already exists") { log.Infof("Delivery Service '%s' already exists", s.XMLID) return nil } } log.Infof("error creating Delivery Service: %v - alerts: %+v", err, alerts.Alerts) return err } enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") err = enc.Encode(&alerts) return err } // enrollDeliveryServicesRequiredCapability takes a json file and creates a DeliveryServicesRequiredCapability object using the TO API func enrollDeliveryServicesRequiredCapability(toSession *session, r io.Reader) error { dec := json.NewDecoder(r) var dsrc tc.DeliveryServicesRequiredCapability err := dec.Decode(&dsrc) if err != nil { log.Infof("error decoding Delivery Services Required Capability: %s\n", err) return err } if dsrc.XMLID == nil { return errors.New("required capability had no XMLID") } opts := client.NewRequestOptions() opts.QueryParameters.Set("xmlId", *dsrc.XMLID) dses, _, err := toSession.GetDeliveryServices(opts) if err != nil { log.Infof("getting Delivery Service by XMLID %s: %s", *dsrc.XMLID, err.Error()) return err } if len(dses.Response) < 1 { err = fmt.Errorf("could not find a Delivey Service with XMLID %s", *dsrc.XMLID) log.Infoln(err) return err } dsrc.DeliveryServiceID = dses.Response[0].ID dsUpdate := dses.Response[0] dsUpdate.RequiredCapabilities = []string{*dsrc.RequiredCapability} sc := tc.ServerCapabilityV5{ Name: *dsrc.RequiredCapability, Description: "description", } _, _, err = toSession.CreateServerCapability(sc, client.RequestOptions{}) if err != nil { log.Infof("error creating Server Capability: %v", err) return err } _, _, err = toSession.UpdateDeliveryService(*dsUpdate.ID, dsUpdate, client.RequestOptions{}) if err != nil { log.Infof("error creating Delivery Services Required Capability: %v", err) return err } return err } func enrollDeliveryServiceServer(toSession *session, r io.Reader) error { dec := json.NewDecoder(r) // DeliveryServiceServers lists ds xmlid and array of server names. Use that to create multiple DeliveryServiceServer objects var dss tc.DeliveryServiceServers err := dec.Decode(&dss) if err != nil { log.Infof("error decoding DeliveryServiceServer: %s\n", err) return err } opts := client.RequestOptions{QueryParameters: url.Values{"xmlId": []string{dss.XmlId}}} dses, _, err := toSession.GetDeliveryServices(opts) if err != nil { return err } if len(dses.Response) == 0 { return errors.New("no deliveryservice with name " + dss.XmlId) } if dses.Response[0].ID == nil { return errors.New("Deliveryservice with name " + dss.XmlId + " has a nil ID") } dsID := *dses.Response[0].ID opts.QueryParameters = url.Values{} var serverIDs []int for _, sn := range dss.ServerNames { opts.QueryParameters.Set("hostName", sn) servers, _, err := toSession.GetServers(opts) if err != nil { return err } if len(servers.Response) == 0 { return errors.New("no server with hostName " + sn) } serverIDs = append(serverIDs, servers.Response[0].ID) } resp, _, err := toSession.CreateDeliveryServiceServers(dsID, serverIDs, true, client.RequestOptions{}) if err != nil { log.Infof("error assigning servers %v to Delivery Service #%d: %v - alerts: %+v", serverIDs, dsID, err, resp.Alerts) } return err } func enrollDivision(toSession *session, r io.Reader) error { dec := json.NewDecoder(r) var s tc.DivisionV5 err := dec.Decode(&s) if err != nil { log.Infof("error decoding Division: %s", err) return err } alerts, _, err := toSession.CreateDivision(s, client.RequestOptions{}) if err != nil { for _, alert := range alerts.Alerts { if strings.Contains(alert.Text, "already exists") { log.Infof("division %s already exists", s.Name) return nil } } log.Infof("error creating Division: %v - alerts: %+v", err, alerts.Alerts) return err } enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") err = enc.Encode(&alerts) return err } func enrollOrigin(toSession *session, r io.Reader) error { dec := json.NewDecoder(r) var s tc.OriginV5 err := dec.Decode(&s) if err != nil { log.Infof("error decoding Origin: %v", err) return err } alerts, _, err := toSession.CreateOrigin(s, client.RequestOptions{}) if err != nil { for _, alert := range alerts.Alerts.Alerts { if alert.Level == tc.ErrorLevel.String() && strings.Contains(alert.Text, "already exists") { log.Infof("Origin '%s' already exists", s.Name) return nil } } log.Infof("error creating Origin: %v - alerts: %+v", err, alerts.Alerts) return err } enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") err = enc.Encode(&alerts) return err } func enrollParameter(toSession *session, r io.Reader) error { dec := json.NewDecoder(r) var params []tc.ParameterV5 err := dec.Decode(&params) if err != nil { log.Infof("error decoding Parameter: %s\n", err) return err } for _, p := range params { eparam, err := toSession.getParameter(p, nil) var alerts tc.Alerts if err == nil { // existing param -- update alerts, _, err = toSession.UpdateParameter(eparam.ID, p, client.RequestOptions{}) if err != nil { log.Infof("error updating parameter %d: %v with %+v - alerts: %+v ", eparam.ID, err, p, alerts.Alerts) break } } else { alerts, _, err = toSession.CreateParameter(p, client.RequestOptions{}) if err != nil { log.Infof("error creating parameter: %v from %+v - alerts: %+v", err, p, alerts.Alerts) return err } eparam, err = toSession.getParameter(p, nil) if err != nil { return err } } // link parameter with profiles if len(p.Profiles) > 0 { var profiles []string err = json.Unmarshal(p.Profiles, &profiles) if err != nil { log.Infof("%v", err) return err } opts := client.NewRequestOptions() for _, n := range profiles { opts.QueryParameters.Set("name", n) profiles, _, err := toSession.GetProfiles(opts) if err != nil { return err } if len(profiles.Response) == 0 { return errors.New("no profile with name " + n) } pp := tc.ProfileParameterCreationRequest{ParameterID: eparam.ID, ProfileID: profiles.Response[0].ID} resp, _, err := toSession.CreateProfileParameter(pp, client.RequestOptions{}) if err != nil { found := false for _, alert := range resp.Alerts { if alert.Level == tc.ErrorLevel.String() && strings.Contains(alert.Text, "already exists") { found = true break } } if found { continue } // the original code didn't actually do anything if the error wasn't that the // Profile/Parameter association already exists. // TODO: handle other errors? } } } enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") err = enc.Encode(&alerts) } return err } func enrollPhysLocation(toSession *session, r io.Reader) error { dec := json.NewDecoder(r) var s tc.PhysLocationV5 err := dec.Decode(&s) if err != nil { err = fmt.Errorf("error decoding Physical Location: %v", err) log.Infoln(err) return err } alerts, _, err := toSession.CreatePhysLocation(s, client.RequestOptions{}) if err != nil { for _, alert := range alerts.Alerts { if alert.Level == tc.ErrorLevel.String() && strings.Contains(alert.Text, "already exists") { log.Infof("Physical Location %s already exists", s.Name) return nil } } err = fmt.Errorf("error creating Physical Location '%s': %v - alerts: %+v", s.Name, err, alerts.Alerts) log.Infoln(err) return err } enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") err = enc.Encode(&alerts) return err } func enrollRegion(toSession *session, r io.Reader) error { dec := json.NewDecoder(r) var s tc.RegionV5 err := dec.Decode(&s) if err != nil { log.Infof("error decoding Region: %s\n", err) return err } alerts, _, err := toSession.CreateRegion(s, client.RequestOptions{}) if err != nil { for _, alert := range alerts.Alerts { if alert.Level == tc.ErrorLevel.String() && strings.Contains(alert.Text, "already exists") { log.Infof("a Region named '%s' already exists", s.Name) return nil } } err = fmt.Errorf("error creating Region '%s': %v - alerts: %+v", s.Name, err, alerts.Alerts) log.Infoln(err) return err } enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") err = enc.Encode(&alerts) return err } func enrollStatus(toSession *session, r io.Reader) error { dec := json.NewDecoder(r) var s tc.StatusV5 err := dec.Decode(&s) if err != nil { log.Infof("error decoding Status: %s", err) return err } alerts, _, err := toSession.CreateStatus(s, client.RequestOptions{}) if err != nil { for _, alert := range alerts.Alerts { if alert.Level == tc.ErrorLevel.String() && strings.Contains(alert.Text, "already exists") { log.Infof("status %s already exists", *s.Name) return nil } } err = fmt.Errorf("error creating Status: %v - alerts: %+v", err, alerts.Alerts) return err } enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") err = enc.Encode(&alerts) return err } func enrollTenant(toSession *session, r io.Reader) error { dec := json.NewDecoder(r) var s tc.TenantV5 err := dec.Decode(&s) if err != nil { log.Infof("error decoding Tenant: %s", err) return err } alerts, _, err := toSession.CreateTenant(s, client.RequestOptions{}) if err != nil { for _, alert := range alerts.Alerts.Alerts { if alert.Level == tc.ErrorLevel.String() && strings.Contains(alert.Text, "already exists") { log.Infof("tenant %s already exists", *s.Name) return nil } } err = fmt.Errorf("error creating Tenant: %v - alerts: %+v", err, alerts.Alerts) log.Infoln(err) return err } enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") err = enc.Encode(&alerts) return err } func enrollUser(toSession *session, r io.Reader) error { dec := json.NewDecoder(r) var s tc.UserV4 err := dec.Decode(&s) log.Infof("User is %++v\n", s) if err != nil { log.Infof("error decoding User: %v", err) return err } alerts, _, err := toSession.CreateUser(s, client.RequestOptions{}) if err != nil { for _, alert := range alerts.Alerts.Alerts { if alert.Level == tc.ErrorLevel.String() && strings.Contains(alert.Text, "already exists") { log.Infof("user %s already exists\n", s.Username) return nil } } err = fmt.Errorf("error creating User: %v - alerts: %+v", err, alerts.Alerts) log.Infoln(err) return err } enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") err = enc.Encode(&alerts) return err } // enrollProfile takes a json file and creates a Profile object using the TO API func enrollProfile(toSession *session, r io.Reader) error { dec := json.NewDecoder(r) var profile tc.ProfileV5 err := dec.Decode(&profile) if err != nil { log.Infof("error decoding Profile: %s\n", err) return err } // get a copy of the parameters parameters := profile.Parameters enc := json.NewEncoder(os.Stdout) enc.SetIndent(" ", "") enc.Encode(profile) if len(profile.Name) == 0 { log.Infoln("missing name on profile") return errors.New("missing name on profile") } opts := client.NewRequestOptions() opts.QueryParameters.Set("name", profile.Name) profiles, _, err := toSession.GetProfiles(opts) createProfile := false if err != nil || len(profiles.Response) == 0 { // no profile by that name -- need to create it createProfile = true } else { // updating - ID needs to match profile = profiles.Response[0] } var alerts tc.Alerts var action string if createProfile { alerts, _, err = toSession.CreateProfile(profile, client.RequestOptions{}) if err != nil { found := false for _, alert := range alerts.Alerts { if alert.Level == tc.ErrorLevel.String() && strings.Contains(alert.Text, "already exists") { found = true break } } if found { log.Infof("profile %s already exists", profile.Name) } else { log.Infof("error creating profile from %+v: %v - alerts: %+v", profile, err, alerts.Alerts) } } profiles, _, err = toSession.GetProfiles(opts) if err != nil { log.Infof("error getting profile ID from %+v: %v - alerts: %+v", profile, err, profiles.Alerts) } if len(profiles.Response) == 0 { err = fmt.Errorf("no results returned for getting profile ID from %+v", profile) log.Infoln(err) return err } profile = profiles.Response[0] action = "creating" } else { alerts, _, err = toSession.UpdateProfile(profile.ID, profile, client.RequestOptions{}) action = "updating" } if err != nil { log.Infof("error "+action+" from %s: %s", err) return err } for _, p := range parameters { var name, configFile, value string var secure bool if p.ConfigFile != nil { configFile = *p.ConfigFile } if p.Name != nil { name = *p.Name } if p.Value != nil { value = *p.Value } param := tc.ParameterV5{ConfigFile: configFile, Name: name, Value: value, Secure: secure} eparam, err := toSession.getParameter(param, nil) if err != nil { // create it log.Infof("creating param %+v", param) newAlerts, _, err := toSession.CreateParameter(param, client.RequestOptions{}) if err != nil { log.Infof("can't create parameter %+v: %s, %v", param, err, newAlerts.Alerts) continue } eparam, err = toSession.getParameter(param, nil) if err != nil { log.Infof("error getting new parameter %+v: \n", param) log.Infof(err.Error()) continue } } else { log.Infof("found param %+v\n", eparam) } if eparam.ID < 1 { log.Infof("param ID not found for %v", eparam) continue } pp := tc.ProfileParameterCreationRequest{ProfileID: profile.ID, ParameterID: eparam.ID} resp, _, err := toSession.CreateProfileParameter(pp, client.RequestOptions{}) if err != nil { found := false for _, alert := range resp.Alerts { if alert.Level == tc.ErrorLevel.String() && strings.Contains(alert.Text, "already exists") { found = true break } } if !found { log.Infof("error creating profileparameter %+v: %v - alerts: %+v", pp, err, resp.Alerts) } } } //enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") err = enc.Encode(&alerts) return err } // enrollServer takes a json file and creates a Server object using the TO API func enrollServer(toSession *session, r io.Reader) error { dec := json.NewDecoder(r) var s tc.ServerV5 err := dec.Decode(&s) if err != nil { log.Infof("error decoding Server: %v", err) return err } alerts, _, err := toSession.CreateServer(s, client.RequestOptions{}) if err != nil { err = fmt.Errorf("error creating Server: %v - alerts: %+v", err, alerts.Alerts) log.Infoln(err) return err } enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") err = enc.Encode(&alerts) return err } // enrollServerCapability takes a json file and creates a ServerCapabilityV41 object using the TO API func enrollServerCapability(toSession *session, r io.Reader) error { dec := json.NewDecoder(r) var s tc.ServerCapabilityV5 err := dec.Decode(&s) if err != nil { err = fmt.Errorf("error decoding Server Capability: %v", err) log.Infoln(err) return err } alerts, _, err := toSession.CreateServerCapability(s, client.RequestOptions{}) if err != nil { err = fmt.Errorf("error creating Server Capability: %v - alerts: %+v", err, alerts.Alerts) log.Infoln(err) return err } enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") err = enc.Encode(&alerts) return err } // enrollFederation takes a json file and creates a Federation object using the TO API. // It also assigns a Delivery Service, the CDN in a Box admin user, IPv4 resolvers, // and IPv6 resolvers to that Federation. func enrollFederation(toSession *session, r io.Reader) error { dec := json.NewDecoder(r) var federation tc.AllDeliveryServiceFederationsMapping err := dec.Decode(&federation) if err != nil { log.Infof("error decoding Server Capability: %s\n", err) return err } opts := client.NewRequestOptions() for _, mapping := range federation.Mappings { if mapping.CName == nil || mapping.TTL == nil { err = fmt.Errorf("mapping found with null or undefined CName and/or TTL: %+v", mapping) log.Errorln(err) return err } var cdnFederation tc.CDNFederationV5 var cdnName string { xmlID := string(federation.DeliveryService) opts.QueryParameters.Set("xmlId", xmlID) deliveryServices, _, err := toSession.GetDeliveryServices(opts) opts.QueryParameters.Del("xmlId") if err != nil { err = fmt.Errorf("getting Delivery Service '%s': %v - alerts: %+v", xmlID, err, deliveryServices.Alerts) log.Infoln(err) return err } if len(deliveryServices.Response) != 1 { err = fmt.Errorf("wanted 1 Delivery Service with XMLID %s but received %d Delivery Services", xmlID, len(deliveryServices.Response)) log.Infoln(err) return err } deliveryService := deliveryServices.Response[0] cdnName = *deliveryService.CDNName cdnFederation = tc.CDNFederationV5{ CName: *mapping.CName, TTL: *mapping.TTL, } resp, _, err := toSession.CreateCDNFederation(cdnFederation, cdnName, client.RequestOptions{}) if err != nil { err = fmt.Errorf("creating CDN Federation: %v - alerts: %+v", err, resp.Alerts) log.Infoln(err) return err } cdnFederation = resp.Response if alerts, _, err := toSession.CreateFederationDeliveryServices(cdnFederation.ID, []int{*deliveryService.ID}, true, client.RequestOptions{}); err != nil { err = fmt.Errorf("assigning Delivery Service %s to Federation with ID %d: %v - alerts: %+v", xmlID, cdnFederation.ID, err, alerts.Alerts) log.Infoln(err) return err } } { user, _, err := toSession.GetUserCurrent(client.RequestOptions{}) if err != nil { err = fmt.Errorf("getting the Current User: %v - alerts: %+v", err, user.Alerts) log.Infoln(err) return err } if user.Response.ID == nil { err = errors.New("current user returned from Traffic Ops had null or undefined ID") log.Infoln(err) return err } resp, _, err := toSession.CreateFederationUsers(cdnFederation.ID, []int{*user.Response.ID}, true, client.RequestOptions{}) if err != nil { username := user.Response.Username err = fmt.Errorf("assigning User '%s' to Federation with ID %d: %v - alerts: %+v", username, cdnFederation.ID, err, resp.Alerts) log.Infoln(err) return err } } var allResolverIDs []int { resolverTypes := []tc.FederationResolverType{tc.FederationResolverType4, tc.FederationResolverType6} resolverArrays := [][]string{mapping.Resolve4, mapping.Resolve6} for index, resolvers := range resolverArrays { resolverIDs, err := createFederationResolversOfType(toSession, resolverTypes[index], resolvers) if err != nil { return err } allResolverIDs = append(allResolverIDs, resolverIDs...) } } if resp, _, err := toSession.AssignFederationFederationResolver(cdnFederation.ID, allResolverIDs, true, client.RequestOptions{}); err != nil { err = fmt.Errorf("assigning Federation Resolvers to Federation with ID %d: %v - alerts: %+v", cdnFederation.ID, err, resp.Alerts) log.Infoln(err) return err } opts.QueryParameters.Set("id", strconv.Itoa(cdnFederation.ID)) response, _, err := toSession.GetCDNFederations(cdnName, opts) opts.QueryParameters.Del("id") if err != nil { err = fmt.Errorf("getting CDN Federation with ID %d: %v - alerts: %+v", cdnFederation.ID, err, response.Alerts) return err } if len(response.Response) < 1 { err = fmt.Errorf("unable to GET a CDN Federation ID %d in CDN %s", cdnFederation.ID, cdnName) log.Infoln(err) return err } cdnFederation = response.Response[0] enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") err = enc.Encode(&cdnFederation) if err != nil { err = fmt.Errorf("encoding CDNFederation %s with ID %d: %v", cdnFederation.CName, cdnFederation.ID, err) log.Infoln(err) return err } } return err } // createFederationResolversOfType creates Federation Resolvers of either RESOLVE4 type or RESOLVE6 type. func createFederationResolversOfType(toSession *session, resolverTypeName tc.FederationResolverType, ipAddresses []string) ([]int, error) { typeNameString := string(resolverTypeName) opts := client.NewRequestOptions() opts.QueryParameters.Set("name", typeNameString) types, _, err := toSession.GetTypes(opts) if err != nil { err = fmt.Errorf("getting resolver type '%s': %v - alerts: %+v", typeNameString, err, types.Alerts) log.Infoln(err) return nil, err } if len(types.Response) < 1 { err := fmt.Errorf("unable to get a type with name %s", typeNameString) log.Infof(err.Error()) return nil, err } typeID := uint(types.Response[0].ID) var resolverIDs []int for _, ipAddress := range ipAddresses { resolver := tc.FederationResolverV5{ IPAddress: &ipAddress, TypeID: &typeID, } response, _, err := toSession.CreateFederationResolver(resolver, client.RequestOptions{}) if err != nil { err = fmt.Errorf("creating Federation Resolver with IP address %s: %v - alerts: %+v", ipAddress, err, response.Alerts) return nil, err } if response.Response.ID == nil { } resolverIDs = append(resolverIDs, int(*response.Response.ID)) } return resolverIDs, nil } // enrollServerServerCapability takes a json file and creates a ServerServerCapability object using the TO API func enrollServerServerCapability(toSession *session, r io.Reader) error { dec := json.NewDecoder(r) var s tc.ServerServerCapabilityV5 err := dec.Decode(&s) if err != nil { err = fmt.Errorf("error decoding Server/Capability relationship: %s", err) log.Infoln(err) return err } if s.Server == nil { err = errors.New("server/Capability relationship did not specify a server") return err } resp, _, err := toSession.GetServers(client.RequestOptions{QueryParameters: url.Values{"hostName": []string{*s.Server}}}) if err != nil { err = fmt.Errorf("getting server '%s': %v - alerts: %+v", *s.Server, err, resp.Alerts) log.Infoln(err) return err } if len(resp.Response) < 1 { err = fmt.Errorf("could not find Server %s", *s.Server) log.Infoln(err.Error()) return err } if len(resp.Response) > 1 { err = fmt.Errorf("found more than 1 Server with hostname %s", *s.Server) log.Infoln(err.Error()) return err } s.ServerID = &resp.Response[0].ID alerts, _, err := toSession.CreateServerServerCapability(s, client.RequestOptions{}) if err != nil { err = fmt.Errorf("error creating Server Server Capability: %v - alerts: %+v", err, alerts.Alerts) log.Infoln(err.Error()) return err } enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") err = enc.Encode(&alerts) return err } type dirWatcher struct { *fsnotify.Watcher TOSession *session watched map[string]func(toSession *session, fn string) error } func newDirWatcher(toSession *session) (*dirWatcher, error) { var err error var dw dirWatcher dw.Watcher, err = fsnotify.NewWatcher() if err != nil { return nil, err } dw.watched = make(map[string]func(toSession *session, fn string) error) go func() { const ( processed = ".processed" rejected = ".rejected" retry = ".retry" ) originalNameRegex := regexp.MustCompile(`(\.retry)*$`) emptyCount := map[string]int{} const maxEmptyTries = 10 for { select { case event, ok := <-dw.Events: if !ok { log.Infoln("event not ok") continue } // ignore all but Create events if event.Op&fsnotify.Create != fsnotify.Create { continue } // skip already processed files if strings.HasSuffix(event.Name, processed) || strings.HasSuffix(event.Name, rejected) { continue } i, err := os.Stat(event.Name) if err != nil || i.IsDir() { log.Infoln("skipping " + event.Name) continue } log.Infoln("new file :", event.Name) // what directory is the file in? Invoke the matching func dir := filepath.Base(filepath.Dir(event.Name)) suffix := rejected if f, ok := dw.watched[dir]; ok { t := filepath.Base(dir) log.Infoln("creating " + t + " from " + event.Name) // Sleep for 100 milliseconds so that the file content is probably there when the directory watcher // sees the file time.Sleep(100 * time.Millisecond) err := f(toSession, event.Name) // If a file is empty, try reading from it 10 times before giving up on that file if err == io.EOF { originalName := originalNameRegex.ReplaceAllString(event.Name, "") if _, exists := emptyCount[originalName]; !exists { emptyCount[originalName] = 0 } emptyCount[originalName]++ log.Infof("empty json object %s: %s\ntried file %d out of %d times", originalName, err.Error(), emptyCount[originalName], maxEmptyTries) if emptyCount[originalName] < maxEmptyTries { newName := event.Name + retry if err := os.Rename(event.Name, newName); err != nil { log.Infof("error renaming %s to %s: %s", event.Name, newName, err) } continue } } if err != nil { log.Infof("error creating %s from %s: %s\n", dir, event.Name, err.Error()) } else { suffix = processed } } else { log.Infof("no method for creating %s\n", dir) } // rename the file indicating if processed or rejected err = os.Rename(event.Name, event.Name+suffix) if err != nil { log.Infof("error renaming %s to %s: %s\n", event.Name, event.Name+suffix, err.Error()) } case err, ok := <-dw.Errors: log.Infof("error from fsnotify: ok? %v; error: %v\n", ok, err) continue } } }() return &dw, err } // watch starts f when a new file is created in dir func (dw *dirWatcher) watch(watchdir, t string, f func(*session, io.Reader) error) { dir := watchdir + "/" + t if stat, err := os.Stat(dir); err != nil || !stat.IsDir() { // attempt to create dir if err = os.Mkdir(dir, os.ModeDir|0700); err != nil { log.Infoln("cannot watch " + dir + ": not a directory") return } } log.Infoln("watching " + dir) dw.Add(dir) dw.watched[t] = func(toSession *session, fn string) error { fh, err := os.Open(fn) if err != nil { return err } defer log.Close(fh, "could not close file") return f(toSession, fh) } } func startWatching(watchDir string, toSession *session, dispatcher map[string]func(*session, io.Reader) error) (*dirWatcher, error) { // watch for file creation in directories dw, err := newDirWatcher(toSession) if err == nil { for d, f := range dispatcher { dw.watch(watchDir, d, f) } } return dw, err } func startServer(httpPort string, toSession *session, dispatcher map[string]func(*session, io.Reader) error) error { baseEP := "/api/4.0/" for d, f := range dispatcher { http.HandleFunc(baseEP+d, func(w http.ResponseWriter, r *http.Request) { defer log.Close(r.Body, "could not close reader") f(toSession, r.Body) }) } go func() { server := &http.Server{ Addr: httpPort, TLSConfig: nil, ErrorLog: log.Error, } if err := server.ListenAndServe(); err != nil { log.Errorf("stopping server: %v\n", err) panic(err) } }() log.Infoln("http service started on " + httpPort) return nil } // Set up the log config -- all messages go to stdout type logConfig struct{} func (cfg logConfig) ErrorLog() log.LogLocation { return log.LogLocationStdout } func (cfg logConfig) WarningLog() log.LogLocation { return log.LogLocationStdout } func (cfg logConfig) InfoLog() log.LogLocation { return log.LogLocationStdout } func (cfg logConfig) DebugLog() log.LogLocation { return log.LogLocationStdout } func (cfg logConfig) EventLog() log.LogLocation { return log.LogLocationStdout } func main() { var watchDir, httpPort string flag.StringVar(&startedFile, "started", startedFile, "file indicating service was started") flag.StringVar(&watchDir, "dir", "", "base directory to watch") flag.StringVar(&httpPort, "http", "", "act as http server for POST on this port (e.g. :7070)") flag.Parse() err := log.InitCfg(logConfig{}) if err != nil { panic(err.Error()) } if watchDir == "" && httpPort == "" { // if neither -dir nor -http provided, default to watching the current dir watchDir = "." } var toCreds struct { URL string `envconfig:"TO_URL"` User string `envconfig:"TO_USER"` Password string `envconfig:"TO_PASSWORD"` } envconfig.Process("", &toCreds) reqTimeout := time.Second * time.Duration(60) log.Infoln("Starting TrafficOps session") toSession, err := newSession(reqTimeout, toCreds.URL, toCreds.User, toCreds.Password) if err != nil { log.Errorln("error starting TrafficOps session: " + err.Error()) os.Exit(1) } log.Infoln("TrafficOps session established") // dispatcher maps an API endpoint name to a function to act on the JSON input Reader dispatcher := map[string]func(*session, io.Reader) error{ "types": enrollType, "cdns": enrollCDN, "cachegroups": enrollCachegroup, "topologies": enrollTopology, "profiles": enrollProfile, "parameters": enrollParameter, "servers": enrollServer, "server_capabilities": enrollServerCapability, "server_server_capabilities": enrollServerServerCapability, "asns": enrollASN, "deliveryservices": enrollDeliveryService, "deliveryservices_required_capabilities": enrollDeliveryServicesRequiredCapability, "deliveryservice_servers": enrollDeliveryServiceServer, "divisions": enrollDivision, "federations": enrollFederation, "origins": enrollOrigin, "phys_locations": enrollPhysLocation, "regions": enrollRegion, "statuses": enrollStatus, "tenants": enrollTenant, "users": enrollUser, } if len(httpPort) != 0 { log.Infoln("Starting http server on " + httpPort) err := startServer(httpPort, &toSession, dispatcher) if err != nil { log.Errorln("http server on " + httpPort + " failed: " + err.Error()) } } if len(watchDir) != 0 { log.Infoln("Watching directory " + watchDir) dw, err := startWatching(watchDir, &toSession, dispatcher) defer log.Close(dw, "could not close dirwatcher") if err != nil { log.Errorf("dirwatcher on %s failed: %s", watchDir, err.Error()) } } // create this file to indicate the enroller is ready f, err := os.Create(startedFile) if err != nil { panic(err) } log.Infoln("Created " + startedFile) log.Close(f, "could not close file") var waitforever chan struct{} <-waitforever }