capistrano-puppeteer 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -2,12 +2,42 @@
2
2
 
3
3
  Some useful capistrano tasks for standalone puppet masterless puppet deployments.
4
4
 
5
+ # Usage
6
+
7
+ ## Launching Amazon Instances
8
+
9
+ Populate ```config/deploy.rb``` with the following attributes
10
+
11
+ ``` ruby
12
+ require 'capistrano/puppeteer/aws'
13
+
14
+ set :cloud_provider, 'AWS'
15
+ set :aws_secret_access_key, 'X...'
16
+ set :aws_access_key_id, 'A...'
17
+ set :aws_region, 'us-west-2'
18
+ set :aws_availability_zone, 'us-west-2a'
19
+ set :aws_ami, 'ami-20800c10' # Precise 64bit http://cloud.ubuntu.com/ami/
20
+ set :aws_key_name, 'default'
21
+ set :aws_iam_role, 'backups' # Optional
22
+ ```
23
+
24
+ ## Bootstrapping an instance
25
+
26
+ Populate ```config/deploy.rb``` with the following attributes
27
+
28
+ ``` ruby
29
+ set :bootstrap_domain, 'example.com'
30
+ set :bootstrap_user, 'johnf'
31
+ set :ssh_key, 'config/aws.pem'
32
+ set :puppet_repo, 'git@github.com:johnf/puppet.git'
33
+ ```
34
+
5
35
  # Installation
6
36
 
7
37
  Add this line to your application's Gemfile:
8
38
 
9
39
  ``` ruby
10
- gem 'puppeteer'
40
+ gem 'capistrano-puppeteer'
11
41
  ```
12
42
 
13
43
  And then execute:
@@ -19,7 +49,7 @@ $ bundle
19
49
  Or install it yourself as:
20
50
 
21
51
  ``` bash
22
- $ gem install puppeteer
52
+ $ gem install capistrano-puppeteer
23
53
  ```
24
54
 
25
55
  Then add it to your _config/deploy.rb_
@@ -28,6 +58,15 @@ Then add it to your _config/deploy.rb_
28
58
  require 'capistrano/puppeteer'
29
59
  ```
30
60
 
61
+ # Configuration
62
+
63
+ Your puppet.conf requires at minimum
64
+
65
+ ``` ini
66
+ [main]
67
+ confdir = .
68
+ ```
69
+
31
70
  ## Usage
32
71
 
33
72
  TODO: Write usage instructions here
@@ -16,4 +16,7 @@ Gem::Specification.new do |gem|
16
16
  gem.version = Capistrano::Puppeteer::VERSION
17
17
 
18
18
  gem.add_dependency 'capistrano'
19
+ gem.add_dependency 'fog', '>= 1.9.0'
20
+
21
+ gem.add_development_dependency 'rake'
19
22
  end
@@ -1,32 +1,5 @@
1
- require 'puppeteer/version'
1
+ require 'capistrano/puppeteer/version'
2
2
  require 'capistrano'
3
3
 
4
- module Capistrano
5
-
6
- class Puppeteer
7
- def self.extended(configuration)
8
- configuration.load do
9
- _cset(:puppet_path) { abort "Please specify the path to puppet, set :puppet_path, '/srv/puppet'" }
10
-
11
- namespace :puppet do
12
- task :update do
13
- run_locally 'git push'
14
- run "cd #{puppet_path} && git pull --quiet"
15
- end
16
-
17
- desc 'Perform a puppet run'
18
- task :go do
19
- update
20
- options = ENV['options'] || ENV['OPTIONS'] || ''
21
- run "cd #{puppet_path} && #{sudo} puppet apply --configfile puppet.conf manifests/site.pp #{options}"
22
- end
23
- end
24
-
25
- end
26
- end
27
- end
28
- end
29
-
30
- if Capistrano::Configuration.instance
31
- Capistrano::Configuration.instance.extend Capistrano::Puppeteer
32
- end
4
+ require 'capistrano/puppeteer/puppet'
5
+ require 'capistrano/puppeteer/bootstrap'
@@ -0,0 +1,188 @@
1
+ require 'fog'
2
+
3
+ module Capistrano
4
+ module Puppeteer
5
+ module AWS
6
+ FLAVOURS = {
7
+ 't1.micro' => {:ram => 0.6, :io => 'low', :compute => 2, :price => 0.02},
8
+
9
+ 'm1.small' => {:ram => 1.7, :io => 'moderate', :compute => 1, :price => 0.08},
10
+ 'm1.medium' => {:ram => 3.75, :io => 'moderate', :compute => 2, :price => 0.16},
11
+ 'm1.large' => {:ram => 7.5, :io => 'high', :compute => 4, :price => 0.32},
12
+ 'm1.xlarge' => {:ram => 15, :io => 'high', :compute => 8, :price => 0.64},
13
+
14
+ 'm2.xlarge' => {:ram => 17.1, :io => 'moderate', :compute => 6.5, :price => 0.45},
15
+ 'm2.2xlarge' => {:ram => 34.2, :io => 'high', :compute => 13, :price => 0.90},
16
+ 'm2.4xlarge' => {:ram => 68.4, :io => 'high', :compute => 26, :price => 1.80},
17
+
18
+ 'c1.medium' => {:ram => 1.7, :io => 'moderate', :compute => 5, :price => 0.165},
19
+ 'c1.xlarge' => {:ram => 7, :io => 'high', :compute => 20, :price => 0.66},
20
+
21
+ 'cc1.4xlarge' => {:ram => 23, :io => 'v.high', :compute => 33.5, :price => 1.30},
22
+ 'cc1.8xlarge' => {:ram => 60.5, :io => 'v.high', :compute => 88 , :price => 2.40},
23
+
24
+ 'cg1.4xlarge' => {:ram => 22, :io => 'v.high', :compute => 33.5, :price => 2.10},
25
+ }
26
+
27
+ def self.extended(configuration)
28
+ configuration.load do
29
+ set(:cloud_provider) { abort "Please specify a cloud provider, set :cloud_provider, 'AWS'" } unless exists? :cloud_provider
30
+ set(:aws_ami) { abort "Please specify a AWS AMI, set :aws_ami, 'ami-a29943cb'" } unless exists? :aws_ami
31
+ set(:aws_secret_access_key) { abort "Please specify an AWS Access Key, set :aws_secret_access_key, 'XXXX'" } unless exists? :aws_secret_access_key
32
+ set(:aws_access_key_id) { abort "Please specify a AWS AMI, set :aws_access_key_id, 'ZZZ'" } unless exists? :aws_access_key_id
33
+ set(:aws_region) { abort "Please specify a AWS AMI, set :aws_region, 'us-west-1'" } unless exists? :aws_availability_zone
34
+ set(:aws_availability_zone) { abort "Please specify a AWS AMI, set :aws_availability_zone, 'us-west-1a'" } unless exists? :aws_availability_zone
35
+ set(:aws_key_name) { abort "Please specify a AWS AMI, set :aws_key_name, 'default'" } unless exists? :aws_key_name
36
+ set(:aws_ssh_key) { abort "Please specify a AWS AMI, set :aws_ssh_key, 'config/aws.pem'" } unless exists? :aws_ssh_key
37
+
38
+ namespace :aws do
39
+
40
+ desc <<-DESC
41
+ create an AWS instance.
42
+
43
+ cap aws:create [OPTIONS]
44
+
45
+ Available options:
46
+
47
+ flavour (required) - The type of EC2 instance to create
48
+ name (required) - The name of the instance, this will be used as the AWS tag
49
+ iam_role - An IAM role to apply to the instance
50
+
51
+ DESC
52
+ task :create do
53
+ flavour = ENV['flavour'] || abort('please specify a flavour')
54
+ name = ENV['name'] || abort('please specify name')
55
+ iam_role = ENV['iam_role']
56
+
57
+ puts "Creating Instance..."
58
+ instance_options = {
59
+ :image_id => aws_ami,
60
+ :availability_zone => aws_availability_zone,
61
+ :flavor_id => flavour,
62
+ :key_name => aws_key_name,
63
+ :tags => { 'Name' => name },
64
+ }
65
+
66
+ instance_options[:iam_instance_profile_name] = iam_role if iam_role
67
+
68
+ server = servers.create instance_options
69
+ server.wait_for { ready? }
70
+ server.reload
71
+ ENV['HOSTS'] = server.public_ip_address
72
+ end
73
+
74
+
75
+ desc 'List Instance types'
76
+ task :flavours do
77
+ puts "%-11s %-11s %-7s %-5s %s" % %w[Name Price/Month RAM Units IO]
78
+ Capistrano::Puppeteer::AWS::FLAVOURS.each do |flavor, opts|
79
+ puts "%-11s $ %7.2f %4.1f GB %4.1f %s" % [flavor, opts[:price] * 720, opts[:ram], opts[:compute], opts[:io]]
80
+ end
81
+ end
82
+
83
+ desc 'List current AWS instances'
84
+ task :list do
85
+ format = '%-15s %-10s %-8s %-10s %-43s %-15s %-10s %-s %s'
86
+ puts format % %w{Name ID State Zone DNS IP Type CreatedAt ImageID}
87
+ servers.sort {|a,b| (a.tags['Name'] || 'Unknown') <=> (b.tags['Name'] || 'Unknown') }.each do |server|
88
+ puts format % [server.tags['Name'], server.id, server.state, server.availability_zone, server.dns_name, server.private_ip_address, server.flavor_id, server.created_at, server.image_id]
89
+ end
90
+ end
91
+
92
+ desc 'Describe an instance'
93
+ desc <<-DESC
94
+ Describe an AWS instance.
95
+
96
+ cap aws:show instance_id=...
97
+
98
+ DESC
99
+ task :show do
100
+ instance_id = ENV['instance_id'] || abort('provide an instance_id')
101
+ server = servers.get instance_id
102
+ p server
103
+ end
104
+
105
+ desc <<-DESC
106
+ Start an AWS instance.
107
+
108
+ cap aws:start instance_id=...
109
+
110
+ DESC
111
+ task :start do
112
+ instance_id = ENV['instance_id'] || abort('provide an instance_id')
113
+
114
+ server = servers.get instance_id
115
+ server.start
116
+ end
117
+
118
+ desc <<-DESC
119
+ Stop an AWS instance.
120
+
121
+ cap aws:stop instance_id=...
122
+
123
+ Options:
124
+ force=true - Forces a stop for a hung instance
125
+
126
+ DESC
127
+ task :stop do
128
+ instance_id = ENV['instance_id'] || abort('provide an instance_id')
129
+ force = ENV['force'] =~ /^true$/i
130
+
131
+ server = servers.get instance_id
132
+ server.stop force
133
+ end
134
+
135
+ desc <<-DESC
136
+ Destroy an AWS instance.
137
+
138
+ cap aws:destroy instance_id=...
139
+
140
+ DESC
141
+ task :destroy do
142
+ instance_id = ENV['instance_id'] || abort('provide an instance_id')
143
+
144
+ server = servers.get instance_id
145
+ server.destroy
146
+ end
147
+
148
+ desc <<-DESC
149
+ Reboot an AWS instance.
150
+
151
+ cap aws:reboot instance_id=...
152
+
153
+ Options:
154
+ force=true - Forces a stop for a hung instance
155
+
156
+ DESC
157
+ task :reboot do
158
+ instance_id = ENV['instance_id'] || abort('provide an instance_id')
159
+ force = ENV['force'] =~ /^true$/i
160
+
161
+ server = servers.get instance_id
162
+ server.reboot force
163
+ end
164
+
165
+ def compute
166
+ @compute ||= Fog::Compute.new(
167
+ :provider => cloud_provider,
168
+ :region => aws_region,
169
+ :aws_secret_access_key => aws_secret_access_key,
170
+ :aws_access_key_id => aws_access_key_id,
171
+ )
172
+ end
173
+
174
+ def servers
175
+ @servers ||= compute.servers
176
+ end
177
+
178
+ end
179
+
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end
185
+
186
+ if Capistrano::Configuration.instance
187
+ Capistrano::Configuration.instance.extend Capistrano::Puppeteer::AWS
188
+ end
@@ -0,0 +1,138 @@
1
+ require 'socket'
2
+ require 'timeout'
3
+
4
+ module Capistrano
5
+ module Puppeteer
6
+ module Bootstrap
7
+
8
+ def self.extended(configuration)
9
+ configuration.load do
10
+ set(:bootstrap_domain) { abort "Please specify a domain, set :bootstrap_domain, 'inodes.org'" } unless exists? :bootstrap_domain
11
+ set(:bootstrap_user) { abort "Please specify a user, set :bootstrap_user, 'johnf'" } unless exists? :bootstrap_domain
12
+ set(:puppet_path) { abort "Please specify the path to puppet, set :puppet_path, '/srv/puppet'" } unless exists? :puppet_path
13
+ set(:puppet_repo) { abort "Please specify the path to puppet, set :puppet_repo, 'git@...'" } unless exists? :puppet_repo
14
+
15
+ namespace :bootstrap do
16
+
17
+ desc 'Create and bootstrap the server'
18
+ task :create do
19
+ puts "NOTE: Add host to puppet first and git push"
20
+ puts
21
+ sleep 5
22
+
23
+ name = ENV['name'] or abort('please supply a name')
24
+ ENV['fqdn'] ||= "#{name}.#{bootstrap_domain}"
25
+ aws.create
26
+ unless wait_for_ssh
27
+ abort "Timed out waiting for SSH to come up on #{ENV['HOSTS']}"
28
+ end
29
+ bootstrap.go
30
+ end
31
+
32
+ def wait_for_ssh
33
+ 60.times do
34
+ begin
35
+ Timeout::timeout(1) do
36
+ s = TCPSocket.new(ENV['HOSTS'], 22)
37
+ s.close
38
+ return true
39
+ end
40
+ rescue Timeout::Error, Errno::ECONNREFUSED, Errno::EHOSTUNREACH
41
+ end
42
+ end
43
+
44
+ return false
45
+ end
46
+
47
+ desc 'Bootstrap the server with puppet'
48
+ task :go do
49
+ if exists? :ssh_key
50
+ system "ssh-add #{ssh_key}"
51
+ end
52
+ hostname
53
+ github
54
+ upgrade
55
+ puppet_setup
56
+ unless ENV['skip_puppet']
57
+ puppet_ubuntu if exists?(:cloud_provider) && cloud_provider == 'AWS'
58
+ puppet.go
59
+ end
60
+ end
61
+
62
+ task :hostname do
63
+ set :user, 'ubuntu' if exists?(:cloud_provider) && cloud_provider == 'AWS'
64
+ fqdn = ENV['fqdn'] || Capistrano::CLI.ui.ask('What is the full fqdn for the host')
65
+ hostname = fqdn.split('.')[0]
66
+
67
+ run "#{sudo} sudo sed -i -e '/127.0.0.1/a127.0.1.1 #{fqdn} #{hostname}' /etc/hosts"
68
+ run "echo #{hostname} | #{sudo} tee /etc/hostname > /dev/null"
69
+ run "#{sudo} sed -itmp -e 's/\\(domain\\|search\\).*/\\1 #{bootstrap_domain}/' /etc/resolv.conf"
70
+ run "#{sudo} service hostname start"
71
+ end
72
+
73
+ task :github do
74
+ set :user, 'ubuntu' if exists?(:cloud_provider) && cloud_provider == 'AWS'
75
+ run "mkdir -p .ssh"
76
+ run "echo 'github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==' >> .ssh/known_hosts"
77
+ end
78
+
79
+ task :upgrade do
80
+ set :user, 'ubuntu' if exists?(:cloud_provider) && cloud_provider == 'AWS'
81
+
82
+ run "#{sudo} apt-get -y update"
83
+ run "DEBIAN_FRONTEND=noninteractive #{sudo} -E apt-get -y dist-upgrade"
84
+ end
85
+
86
+ def remote_file_exists?(full_path)
87
+ 'true' == capture("if [ -e #{full_path} ]; then echo 'true'; fi").strip
88
+ end
89
+
90
+ task :puppet_setup do
91
+ set :user, 'ubuntu' if exists?(:cloud_provider) && cloud_provider == 'AWS'
92
+
93
+ release = capture 'lsb_release --codename --short | tr -d "\n"'
94
+ fail "unable to determine distro release" if release.empty?
95
+
96
+ # add puppetlabs apt repos
97
+ unless remote_file_exists? puppet_path
98
+ filename = '/etc/apt/sources.list.d/puppetlabs.list'
99
+ run "echo 'deb http://apt.puppetlabs.com/ #{release} main' | #{sudo} tee #{filename}"
100
+ end
101
+ run "#{sudo} apt-key adv --keyserver keyserver.ubuntu.com --recv 4BD6EC30"
102
+ run "#{sudo} apt-get -yq update"
103
+ run "#{sudo} apt-get install -y puppet libaugeas-ruby git"
104
+
105
+ unless remote_file_exists? puppet_path
106
+ run "git clone #{puppet_repo} /tmp/puppet"
107
+ run "#{sudo} mv /tmp/puppet #{puppet_path}"
108
+ end
109
+
110
+ run "#{sudo} mkdir -p /home/#{bootstrap_user}/.ssh"
111
+ run "#{sudo} echo 'github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==' | #{sudo} tee /home/#{bootstrap_user}/.ssh/known_hosts"
112
+ end
113
+
114
+ task :puppet_ubuntu do
115
+ set :user, 'ubuntu'
116
+
117
+ run 'cd /srv/puppet && git pull'
118
+
119
+ puppet.go
120
+ end
121
+
122
+ task :reboot do
123
+ set :user, ENV['USER']
124
+
125
+ run "#{sudo} reboot"
126
+ end
127
+
128
+ end
129
+
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
135
+
136
+ if Capistrano::Configuration.instance
137
+ Capistrano::Configuration.instance.extend Capistrano::Puppeteer::Bootstrap
138
+ end
@@ -0,0 +1,40 @@
1
+ module Capistrano
2
+ module Puppeteer
3
+ module Puppet
4
+
5
+ def self.extended(configuration)
6
+ configuration.load do
7
+ unless exists? :puppet_path
8
+ set(:puppet_path) { '/srv/puppet' } unless exists? :puppet_path
9
+ end
10
+
11
+ namespace :puppet do
12
+ task :update do
13
+ system 'git push'
14
+ run "#{sudo} chgrp -R adm #{puppet_path}"
15
+ run "#{sudo} chmod -R g+rw #{puppet_path}"
16
+ run "cd #{puppet_path} && git pull --quiet"
17
+ end
18
+
19
+ desc <<-DESC
20
+ Perform a puppet run.
21
+
22
+ Pass options to puppt using OPTIONS
23
+
24
+ puppet:go options="--noop"
25
+ DESC
26
+ task :go do
27
+ update
28
+ options = ENV['options'] || ENV['OPTIONS'] || ''
29
+ run "cd #{puppet_path} && #{sudo} puppet apply --config puppet.conf --verbose #{options} manifests/site.pp"
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ if Capistrano::Configuration.instance
39
+ Capistrano::Configuration.instance.extend Capistrano::Puppeteer::Puppet
40
+ end
@@ -1,5 +1,5 @@
1
1
  module Capistrano
2
- class Puppeteer
3
- VERSION = "0.0.1"
2
+ module Puppeteer
3
+ VERSION = "0.0.2"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: capistrano-puppeteer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-06-15 00:00:00.000000000 Z
12
+ date: 2013-02-24 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: capistrano
@@ -27,6 +27,38 @@ dependencies:
27
27
  - - ! '>='
28
28
  - !ruby/object:Gem::Version
29
29
  version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: fog
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: 1.9.0
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: 1.9.0
46
+ - !ruby/object:Gem::Dependency
47
+ name: rake
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
30
62
  description: Some useful capistrano tasks for standalone puppet masterless puppet
31
63
  deployments.
32
64
  email:
@@ -42,6 +74,9 @@ files:
42
74
  - Rakefile
43
75
  - capistrano-puppeteer.gemspec
44
76
  - lib/capistrano/puppeteer.rb
77
+ - lib/capistrano/puppeteer/aws.rb
78
+ - lib/capistrano/puppeteer/bootstrap.rb
79
+ - lib/capistrano/puppeteer/puppet.rb
45
80
  - lib/capistrano/puppeteer/version.rb
46
81
  homepage: https://github.com/johnf/capistrano-puppeteer
47
82
  licenses: []
@@ -55,12 +90,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
55
90
  - - ! '>='
56
91
  - !ruby/object:Gem::Version
57
92
  version: '0'
93
+ segments:
94
+ - 0
95
+ hash: -2726431899788713285
58
96
  required_rubygems_version: !ruby/object:Gem::Requirement
59
97
  none: false
60
98
  requirements:
61
99
  - - ! '>='
62
100
  - !ruby/object:Gem::Version
63
101
  version: '0'
102
+ segments:
103
+ - 0
104
+ hash: -2726431899788713285
64
105
  requirements: []
65
106
  rubyforge_project:
66
107
  rubygems_version: 1.8.23
@@ -68,4 +109,3 @@ signing_key:
68
109
  specification_version: 3
69
110
  summary: Capistrano tasks for puppet
70
111
  test_files: []
71
- has_rdoc: