func (e *DNSName) Find(c *fi.Context) (*DNSName, error) { cloud := c.Cloud.(*awsup.AWSCloud) findName := fi.StringValue(e.Name) if findName == "" { return nil, nil } findName = strings.TrimSuffix(findName, ".") findType := fi.StringValue(e.ResourceType) if findType == "" { return nil, nil } request := &route53.ListResourceRecordSetsInput{ HostedZoneId: e.Zone.ID, // TODO: Start at correct name? } var found *route53.ResourceRecordSet err := cloud.Route53.ListResourceRecordSetsPages(request, func(p *route53.ListResourceRecordSetsOutput, lastPage bool) (shouldContinue bool) { for _, rr := range p.ResourceRecordSets { resourceType := aws.StringValue(rr.Type) name := aws.StringValue(rr.Name) glog.V(4).Infof("Found DNS resource %q %q", resourceType, name) if findType != resourceType { continue } name = strings.TrimSuffix(name, ".") if name == findName { found = rr break } } // TODO: Also exit if we are on the 'next' name? return found == nil }) if err != nil { return nil, fmt.Errorf("error listing DNS ResourceRecords: %v", err) } if found == nil { return nil, nil } actual := &DNSName{} actual.Zone = e.Zone actual.Name = e.Name actual.ResourceType = e.ResourceType return actual, nil }
func (_ *File) RenderCloudInit(t *cloudinit.CloudInitTarget, a, e, changes *File) error { dirMode := os.FileMode(0755) fileMode, err := fi.ParseFileMode(fi.StringValue(e.Mode), 0644) if err != nil { return fmt.Errorf("invalid file mode for %q: %q", e.Path, e.Mode) } if e.Type == FileType_Symlink { t.AddCommand(cloudinit.Always, "ln", "-s", fi.StringValue(e.Symlink), e.Path) } else if e.Type == FileType_Directory { parent := filepath.Dir(strings.TrimSuffix(e.Path, "/")) t.AddCommand(cloudinit.Once, "mkdir", "-p", "-m", fi.FileModeToString(dirMode), parent) t.AddCommand(cloudinit.Once, "mkdir", "-m", fi.FileModeToString(dirMode), e.Path) } else if e.Type == FileType_File { err = t.WriteFile(e.Path, e.Contents, fileMode, dirMode) if err != nil { return err } } else { return fmt.Errorf("File type=%q not valid/supported", e.Type) } if e.Owner != nil || e.Group != nil { t.Chown(e.Path, fi.StringValue(e.Owner), fi.StringValue(e.Group)) } if e.OnChangeExecute != nil { t.AddCommand(cloudinit.Always, e.OnChangeExecute...) } return nil }
func (e *LoadBalancer) Find(c *fi.Context) (*LoadBalancer, error) { cloud := c.Cloud.(*awsup.AWSCloud) elbName := fi.StringValue(e.ID) if elbName == "" { elbName = fi.StringValue(e.Name) } lb, err := findELB(cloud, elbName) if err != nil { return nil, err } if lb == nil { return nil, nil } actual := &LoadBalancer{} actual.Name = e.Name actual.ID = lb.LoadBalancerName actual.DNSName = lb.DNSName actual.HostedZoneId = lb.CanonicalHostedZoneNameID for _, subnet := range lb.Subnets { actual.Subnets = append(actual.Subnets, &Subnet{ID: subnet}) } for _, sg := range lb.SecurityGroups { actual.SecurityGroups = append(actual.SecurityGroups, &SecurityGroup{ID: sg}) } actual.Listeners = make(map[string]*LoadBalancerListener) for _, ld := range lb.ListenerDescriptions { l := ld.Listener loadBalancerPort := strconv.FormatInt(aws.Int64Value(l.LoadBalancerPort), 10) actualListener := &LoadBalancerListener{} actualListener.InstancePort = int(aws.Int64Value(l.InstancePort)) actual.Listeners[loadBalancerPort] = actualListener } // Avoid spurious mismatches if subnetSlicesEqualIgnoreOrder(actual.Subnets, e.Subnets) { actual.Subnets = e.Subnets } if e.DNSName == nil { e.DNSName = actual.DNSName } if e.HostedZoneId == nil { e.HostedZoneId = actual.HostedZoneId } if e.ID == nil { e.ID = actual.ID } return actual, nil }
func (e *DNSZone) findExisting(cloud *awsup.AWSCloud) (*route53.HostedZone, error) { findName := fi.StringValue(e.Name) if findName == "" { return nil, nil } if !strings.HasSuffix(findName, ".") { findName += "." } request := &route53.ListHostedZonesByNameInput{ DNSName: aws.String(findName), } response, err := cloud.Route53.ListHostedZonesByName(request) if err != nil { return nil, fmt.Errorf("error listing DNS HostedZones: %v", err) } var zones []*route53.HostedZone for _, zone := range response.HostedZones { if aws.StringValue(zone.Name) == findName { zones = append(zones, zone) } } if len(zones) == 0 { return nil, nil } if len(zones) != 1 { return nil, fmt.Errorf("found multiple hosted zones matched name %q", findName) } return zones[0], nil }
func (e *LoadBalancerHealthChecks) Find(c *fi.Context) (*LoadBalancerHealthChecks, error) { cloud := c.Cloud.(*awsup.AWSCloud) elbName := fi.StringValue(e.LoadBalancer.ID) lb, err := findELB(cloud, elbName) if err != nil { return nil, err } if lb == nil { return nil, nil } actual := &LoadBalancerHealthChecks{} actual.LoadBalancer = e.LoadBalancer if lb.HealthCheck != nil { actual.Target = lb.HealthCheck.Target actual.HealthyThreshold = lb.HealthCheck.HealthyThreshold actual.UnhealthyThreshold = lb.HealthCheck.UnhealthyThreshold actual.Interval = lb.HealthCheck.Interval actual.Timeout = lb.HealthCheck.Timeout } return actual, nil }
func (_ *VPC) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *VPC) error { shared := fi.BoolValue(e.Shared) if shared { // Verify the VPC was found and matches our required settings if a == nil { return fmt.Errorf("VPC with id %q not found", fi.StringValue(e.ID)) } if changes != nil && changes.EnableDNSSupport != nil { return fmt.Errorf("VPC with id %q was set to be shared, but did not have EnableDNSSupport=true", fi.StringValue(e.ID)) } if changes != nil && changes.EnableDNSHostnames != nil { return fmt.Errorf("VPC with id %q was set to be shared, but did not have EnableDNSHostnames=true", fi.StringValue(e.ID)) } return nil } if a == nil { glog.V(2).Infof("Creating VPC with CIDR: %q", *e.CIDR) request := &ec2.CreateVpcInput{ CidrBlock: e.CIDR, } response, err := t.Cloud.EC2.CreateVpc(request) if err != nil { return fmt.Errorf("error creating VPC: %v", err) } e.ID = response.Vpc.VpcId } if changes.EnableDNSSupport != nil { request := &ec2.ModifyVpcAttributeInput{ VpcId: e.ID, EnableDnsSupport: &ec2.AttributeBooleanValue{Value: changes.EnableDNSSupport}, } _, err := t.Cloud.EC2.ModifyVpcAttribute(request) if err != nil { return fmt.Errorf("error modifying VPC attribute: %v", err) } } if changes.EnableDNSHostnames != nil { request := &ec2.ModifyVpcAttributeInput{ VpcId: e.ID, EnableDnsHostnames: &ec2.AttributeBooleanValue{Value: changes.EnableDNSHostnames}, } _, err := t.Cloud.EC2.ModifyVpcAttribute(request) if err != nil { return fmt.Errorf("error modifying VPC attribute: %v", err) } } return t.AddAWSTags(*e.ID, t.Cloud.BuildTags(e.Name)) }
func (s *DNSName) CheckChanges(a, e, changes *DNSName) error { if a == nil { if fi.StringValue(e.Name) == "" { return fi.RequiredField("Name") } } return nil }
func (s *IAMInstanceProfile) CheckChanges(a, e, changes *IAMInstanceProfile) error { if a != nil { if fi.StringValue(e.Name) == "" { return fi.RequiredField("Name") } } return nil }
func (_ *Package) RenderLocal(t *local.LocalTarget, a, e, changes *Package) error { if changes.Version != nil { glog.Infof("Installing package %q", e.Name) if e.Source != nil { // Install a deb local := path.Join(localPackageDir, e.Name) err := os.MkdirAll(localPackageDir, 0755) if err != nil { return fmt.Errorf("error creating directories %q: %v", path.Dir(local), err) } var hash *hashing.Hash if fi.StringValue(e.Hash) != "" { parsed, err := hashing.FromString(fi.StringValue(e.Hash)) if err != nil { return fmt.Errorf("error paring hash: %v", err) } hash = parsed } _, err = fi.DownloadURL(fi.StringValue(e.Source), local, hash) if err != nil { return err } args := []string{"dpkg", "-i", local} glog.Infof("running command %s", args) cmd := exec.Command(args[0], args[1:]...) output, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("error installing package %q: %v: %s", e.Name, err, string(output)) } } else { args := []string{"apt-get", "install", "--yes", e.Name} glog.Infof("running command %s", args) cmd := exec.Command(args[0], args[1:]...) output, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("error installing package %q: %v: %s", e.Name, err, string(output)) } } } return nil }
func (e *VPC) Find(c *fi.Context) (*VPC, error) { cloud := c.Cloud.(*awsup.AWSCloud) request := &ec2.DescribeVpcsInput{} if fi.StringValue(e.ID) != "" { request.VpcIds = []*string{e.ID} } else { request.Filters = cloud.BuildFilters(e.Name) } response, err := cloud.EC2.DescribeVpcs(request) if err != nil { return nil, fmt.Errorf("error listing VPCs: %v", err) } if response == nil || len(response.Vpcs) == 0 { return nil, nil } if len(response.Vpcs) != 1 { return nil, fmt.Errorf("found multiple VPCs matching tags") } vpc := response.Vpcs[0] actual := &VPC{ ID: vpc.VpcId, CIDR: vpc.CidrBlock, Name: findNameTag(vpc.Tags), } glog.V(4).Infof("found matching VPC %v", actual) if actual.ID != nil { request := &ec2.DescribeVpcAttributeInput{VpcId: actual.ID, Attribute: aws.String(ec2.VpcAttributeNameEnableDnsSupport)} response, err := cloud.EC2.DescribeVpcAttribute(request) if err != nil { return nil, fmt.Errorf("error querying for dns support: %v", err) } actual.EnableDNSSupport = response.EnableDnsSupport.Value } if actual.ID != nil { request := &ec2.DescribeVpcAttributeInput{VpcId: actual.ID, Attribute: aws.String(ec2.VpcAttributeNameEnableDnsHostnames)} response, err := cloud.EC2.DescribeVpcAttribute(request) if err != nil { return nil, fmt.Errorf("error querying for dns support: %v", err) } actual.EnableDNSHostnames = response.EnableDnsHostnames.Value } // Prevent spurious comparison failures actual.Shared = e.Shared if e.ID == nil { e.ID = actual.ID } return actual, nil }
func (e *SSHKey) Find(c *fi.Context) (*SSHKey, error) { cloud := c.Cloud.(*awsup.AWSCloud) request := &ec2.DescribeKeyPairsInput{ KeyNames: []*string{e.Name}, } response, err := cloud.EC2.DescribeKeyPairs(request) if awsErr, ok := err.(awserr.Error); ok { if awsErr.Code() == "InvalidKeyPair.NotFound" { return nil, nil } } if err != nil { return nil, fmt.Errorf("error listing SSHKeys: %v", err) } if response == nil || len(response.KeyPairs) == 0 { return nil, nil } if len(response.KeyPairs) != 1 { return nil, fmt.Errorf("Found multiple SSHKeys with Name %q", *e.Name) } k := response.KeyPairs[0] actual := &SSHKey{ Name: k.KeyName, KeyFingerprint: k.KeyFingerprint, } // Avoid spurious changes if fi.StringValue(actual.KeyFingerprint) == fi.StringValue(e.KeyFingerprint) { glog.V(2).Infof("SSH key fingerprints match; assuming public keys match") actual.PublicKey = e.PublicKey } else { glog.V(2).Infof("Computed SSH key fingerprint mismatch: %q %q", fi.StringValue(e.KeyFingerprint), fi.StringValue(actual.KeyFingerprint)) } return actual, nil }
func (e *InternetGateway) Find(c *fi.Context) (*InternetGateway, error) { cloud := c.Cloud.(*awsup.AWSCloud) request := &ec2.DescribeInternetGatewaysInput{} shared := fi.BoolValue(e.Shared) if shared { if fi.StringValue(e.VPC.ID) == "" { return nil, fmt.Errorf("VPC ID is required when InternetGateway is shared") } request.Filters = []*ec2.Filter{awsup.NewEC2Filter("attachment.vpc-id", *e.VPC.ID)} } else { if e.ID != nil { request.InternetGatewayIds = []*string{e.ID} } else { request.Filters = cloud.BuildFilters(e.Name) } } response, err := cloud.EC2.DescribeInternetGateways(request) if err != nil { return nil, fmt.Errorf("error listing InternetGateways: %v", err) } if response == nil || len(response.InternetGateways) == 0 { return nil, nil } if len(response.InternetGateways) != 1 { return nil, fmt.Errorf("found multiple InternetGateways matching tags") } igw := response.InternetGateways[0] actual := &InternetGateway{ ID: igw.InternetGatewayId, Name: findNameTag(igw.Tags), } glog.V(2).Infof("found matching InternetGateway %q", *actual.ID) for _, attachment := range igw.Attachments { actual.VPC = &VPC{ID: attachment.VpcId} } // Prevent spurious comparison failures actual.Shared = e.Shared if e.ID == nil { e.ID = actual.ID } return actual, nil }
func (s *LoadBalancer) CheckChanges(a, e, changes *LoadBalancer) error { if a == nil { if fi.StringValue(e.Name) == "" { return fi.RequiredField("Name") } if len(e.SecurityGroups) == 0 { return fi.RequiredField("SecurityGroups") } if len(e.Subnets) == 0 { return fi.RequiredField("Subnets") } } return nil }
func (_ *Secret) Render(c *fi.Context, a, e, changes *Secret) error { name := fi.StringValue(e.Name) if name == "" { return fi.RequiredField("Name") } secrets := c.SecretStore _, _, err := secrets.GetOrCreateSecret(name) if err != nil { return fmt.Errorf("error creating secret %q: %v", name, err) } return nil }
func (_ *IPAddress) RenderGCE(t *gce.GCEAPITarget, a, e, changes *IPAddress) error { addr := &compute.Address{ Name: *e.Name, Address: fi.StringValue(e.Address), Region: t.Cloud.Region, } if a == nil { glog.Infof("GCE creating address: %q", addr.Name) _, err := t.Cloud.Compute.Addresses.Insert(t.Cloud.Project, t.Cloud.Region, addr).Do() if err != nil { return fmt.Errorf("error creating IPAddress: %v", err) } } else { return fmt.Errorf("Cannot apply changes to IPAddress: %v", changes) } return nil }
func (e *Keypair) Find(c *fi.Context) (*Keypair, error) { castore := c.CAStore name := fi.StringValue(e.Name) if name == "" { return nil, nil } cert, err := castore.FindCert(name) if err != nil { return nil, err } if cert == nil { return nil, nil } key, err := castore.FindPrivateKey(name) if err != nil { return nil, err } if key == nil { return nil, fmt.Errorf("found cert in store, but did not find private key: %q", name) } var alternateNames []string alternateNames = append(alternateNames, cert.Certificate.DNSNames...) alternateNames = append(alternateNames, cert.Certificate.EmailAddresses...) for _, ip := range cert.Certificate.IPAddresses { alternateNames = append(alternateNames, ip.String()) } sort.Strings(alternateNames) actual := &Keypair{ Name: &name, Subject: pkixNameToString(&cert.Subject), AlternateNames: alternateNames, Type: buildTypeDescription(cert.Certificate), } return actual, nil }
func (e *Secret) Find(c *fi.Context) (*Secret, error) { secrets := c.SecretStore name := fi.StringValue(e.Name) if name == "" { return nil, nil } secret, err := secrets.FindSecret(name) if err != nil { return nil, err } if secret == nil { return nil, nil } actual := &Secret{ Name: &name, } return actual, nil }
func (_ *File) RenderLocal(t *local.LocalTarget, a, e, changes *File) error { dirMode := os.FileMode(0755) fileMode, err := fi.ParseFileMode(fi.StringValue(e.Mode), 0644) if err != nil { return fmt.Errorf("invalid file mode for %q: %q", e.Path, fi.StringValue(e.Mode)) } if a != nil { if e.IfNotExists { glog.V(2).Infof("file exists and IfNotExists set; skipping %q", e.Path) return nil } } changed := false if e.Type == FileType_Symlink { if changes.Symlink != nil { // This will currently fail if the target already exists. // That's probably a good thing for now ... it is hard to know what to do here! glog.Infof("Creating symlink %q -> %q", e.Path, *changes.Symlink) err := os.Symlink(*changes.Symlink, e.Path) if err != nil { return fmt.Errorf("error creating symlink %q -> %q: %v", e.Path, *changes.Symlink, err) } changed = true } } else if e.Type == FileType_Directory { if a == nil { parent := filepath.Dir(strings.TrimSuffix(e.Path, "/")) err := os.MkdirAll(parent, dirMode) if err != nil { return fmt.Errorf("error creating parent directories %q: %v", parent, err) } err = os.MkdirAll(e.Path, fileMode) if err != nil { return fmt.Errorf("error creating directory %q: %v", e.Path, err) } changed = true } } else if e.Type == FileType_File { if changes.Contents != nil { err = fi.WriteFile(e.Path, e.Contents, fileMode, dirMode) if err != nil { return fmt.Errorf("error copying file %q: %v", e.Path, err) } changed = true } } else { return fmt.Errorf("File type=%q not valid/supported", e.Type) } if changes.Mode != nil { modeChanged, err := fi.EnsureFileMode(e.Path, fileMode) if err != nil { return fmt.Errorf("error changing mode on %q: %v", e.Path, err) } changed = changed || modeChanged } if changes.Owner != nil || changes.Group != nil { ownerChanged, err := fi.EnsureFileOwner(e.Path, fi.StringValue(e.Owner), fi.StringValue(e.Group)) if err != nil { return fmt.Errorf("error changing owner/group on %q: %v", e.Path, err) } changed = changed || ownerChanged } if changed && e.OnChangeExecute != nil { args := e.OnChangeExecute human := strings.Join(args, " ") glog.Infof("Changed; will execute OnChangeExecute command: %q", human) cmd := exec.Command(args[0], args[1:]...) output, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("error executing command %q: %v\nOutput: %s", human, err, output) } } return nil }
func (_ *Keypair) Render(c *fi.Context, a, e, changes *Keypair) error { name := fi.StringValue(e.Name) if name == "" { return fi.RequiredField("Name") } castore := c.CAStore template, err := buildCertificateTemplate(e.Type) if err != nil { return err } subjectPkix, err := parsePkixName(e.Subject) if err != nil { return fmt.Errorf("error parsing Subject: %v", err) } if len(subjectPkix.ToRDNSequence()) == 0 { return fmt.Errorf("Subject name was empty for SSL keypair %q", name) } template.Subject = *subjectPkix var alternateNames []string alternateNames = append(alternateNames, e.AlternateNames...) for _, san := range alternateNames { san = strings.TrimSpace(san) if san == "" { continue } if ip := net.ParseIP(san); ip != nil { template.IPAddresses = append(template.IPAddresses, ip) } else { template.DNSNames = append(template.DNSNames, san) } } createCertificate := false if a == nil { createCertificate = true } else if changes != nil { if changes.AlternateNames != nil { createCertificate = true } else { glog.Warningf("Ignoring changes in key: %v", fi.DebugAsJsonString(changes)) } } if createCertificate { glog.V(2).Infof("Creating PKI keypair %q", name) // TODO: Reuse private key if already exists? cert, _, err := castore.CreateKeypair(name, template) if err != nil { return err } glog.V(8).Infof("created certificate %v", cert) } // TODO: Check correct subject / flags return nil }
func (_ *Service) RenderLocal(t *local.LocalTarget, a, e, changes *Service) error { serviceName := e.Name action := "" if changes.Running != nil && fi.BoolValue(e.ManageState) { if fi.BoolValue(e.Running) { action = "restart" } else { action = "stop" } } if changes.Definition != nil { servicePath := path.Join(systemdSystemPath, serviceName) err := fi.WriteFile(servicePath, fi.NewStringResource(*e.Definition), 0644, 0755) if err != nil { return fmt.Errorf("error writing systemd service file: %v", err) } glog.Infof("Reloading systemd configuration") cmd := exec.Command("systemctl", "daemon-reload") output, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("error doing systemd daemon-reload: %v\nOutput: %s", err, output) } } // "SmartRestart" - look at the obvious dependencies in the systemd service, restart if start time older if fi.BoolValue(e.ManageState) && fi.BoolValue(e.SmartRestart) { definition := fi.StringValue(e.Definition) if definition == "" && a != nil { definition = fi.StringValue(a.Definition) } if action == "" && fi.BoolValue(e.Running) && definition != "" { dependencies, err := getSystemdDependencies(serviceName, definition) if err != nil { return err } var newest time.Time for _, dependency := range dependencies { stat, err := os.Stat(dependency) if err != nil { glog.Infof("Ignoring error checking service dependency %q: %v", dependency, err) continue } modTime := stat.ModTime() if newest.IsZero() || newest.Before(modTime) { newest = modTime } } if !newest.IsZero() { properties, err := getSystemdStatus(e.Name) if err != nil { return err } startedAt := properties["ExecMainStartTimestamp"] if startedAt == "" { glog.Warningf("service was running, but did not have ExecMainStartTimestamp: %q", serviceName) } else { startedAtTime, err := time.Parse("Mon 2006-01-02 15:04:05 MST", startedAt) if err != nil { return fmt.Errorf("unable to parse service ExecMainStartTimestamp: %q", startedAt) } if startedAtTime.Before(newest) { glog.V(2).Infof("will restart service %q because dependency changed after service start", serviceName) action = "restart" } else { glog.V(2).Infof("will not restart service %q - started after dependencies", serviceName) } } } } } if action != "" && fi.BoolValue(e.ManageState) { glog.Infof("Restarting service %q", serviceName) cmd := exec.Command("systemctl", action, serviceName) output, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("error doing systemd %s %s: %v\nOutput: %s", action, serviceName, err, output) } } return nil }