-
Notifications
You must be signed in to change notification settings - Fork 8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Service and task definition setup #16
Conversation
DE-11289 #time 90m
DE-11289 #time 30m
container_def = new_task_def[:container_definitions].select { |c| c[:name] == family }.first | ||
container_def[:environment] = @deploy_config.env_vars | ||
container_def[:image] = image_tag | ||
container_def[:command] = @deploy_config.command |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
container_def
was never being used; not sure if that was a bug or just leftover cruft lying around.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is actually modifying object data within new_task_def
, which is used on L149 to generate a task definition revision. Admittedly not very clearly though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i see it now... seems like the create_task_definition
method stuff could all just live in update_task
actually?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, create_task_definition
wouldn't be needed - update_task
basically grabs the latest task definition through create_new_task_revision
, deletes some unneeded fields, injects in configurations into the container relevant to broadside, then makes the api call to generate a new revision from that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah so update_task could just create the first task definition if there wasn't one (and if @task_definition_config
it was configured appropriately)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what's the difference between @command
and @deploy_config.command
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@command
is an instance variable belonging to https://github.com/lumoslabs/broadside/blob/master/lib/broadside/configuration/deploy.rb#L45
@deploy_config
belongs to this class, basically a copy of the configuration object above (https://github.com/lumoslabs/broadside/blob/master/lib/broadside/deploy.rb#L8). This was done so that an instance of a deploy would not corrupt data in the original configuration object.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i still don't really understand it (and a few other fields) is duplicated between the two objects? AFAICT the @command
passed to AWS is always and every time based on the command in the broadside config?
resp = ecs_client.describe_services({cluster: config.ecs.cluster, services: [family]}) | ||
unless resp.failures.empty? | ||
exception "Could not find ECS service '#{family}' in cluster '#{config.ecs.cluster}' !" | ||
def all_results(method, key, args = {}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'm a fan
@@ -22,14 +22,18 @@ class DeployConfig < ConfigStruct | |||
:env_vars, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there's actually a spec for this file, mind making some additions there?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i was actually hoping to add a lot of specs with the stub_responses
part of the Aws
gem but i wanted to get some buy in that this approach basically makes sense before doing that (hence the WIP
in the title)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
heh, that's a lot of work - i wouldn't worry about speccing ecs.rb
for now, let's talk about that separately
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's really hard to jump into a project and change critical code without specs! i would feel like 1000% more confident about this PR if running rspec actually confirmed the right calls were made to AWS.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you know that Aws::Client
already provides stubs, right? https://docs.aws.amazon.com/sdkforruby/api/Aws/ClientStubs.html
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
very true. i'll welcome whatever specs you want to supplement this PR with
unless resp.failures.empty? | ||
exception "Could not find ECS service '#{family}' in cluster '#{config.ecs.cluster}' !" | ||
def all_results(method, key, args = {}) | ||
page = ecs.send(method, args) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public_send
, please
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
one of these days i'll remember that public_send
exists before you remind me
@@ -326,6 +338,10 @@ def create_new_task_revision | |||
task_def | |||
end | |||
|
|||
def service_exists? | |||
!ecs_client.describe_services({ cluster: config.ecs.cluster, services: [family] }).failures.empty? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
any?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the AWS services don't really return enumerables, they return weird Seahorse
data structures with a .failures
section.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just saying failures.any?
is clearer than !failures.empty?
. is that not possible?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah i see; yeah that makes more sense.
name: name, | ||
command: @command, | ||
cpu: 1, | ||
essential: true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i almost want these values to be in a default constant that get merged with the name and command stuff
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i thought about that. you can't have a "constant" really because a name, command, family are all dynamic. so i thought about making building the config a separate method but really having a separate method is almost the same as having a method with one line call that builds the aws definition hash.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, you could:
DEFAULT_CONTAINER_DEFINITION = {
cpu: 1,
essential: true,
memory: 1000
}
...
ecs.register_task_definition(
{
container_definitions: [
{
name: name,
command: @command
}.merge(DEFAULT_CONTAINER_DEFINITION)
],
family: name
}.deep_merge(options)
)
but not sure that's much clearer
looks pretty solid in general |
DE-11289 #time 30m
DE-11289 #time 30m
DE-11289 #time 15m
DE-11289 #time 10m
DE-11289 #time 15m
DE-11289 #time 30m
DE-11289 #time 5m
DE-11289 #time 20m
DE-11289 #time 10m
DE-11289 #time 45m
DE-11289
DE-11289 #time 5m
ok @matl33t:
look it over carefully but i think this should be basically ready. |
DE-11289 #time 5m
@matl33t the easiest thing to do might be for you to just make a new branch off this one and add the also travis is having issues because there's no configured key set for AWS. not sure what the preferred approach is there but probably setting an env variable would do the trick. |
) | ||
|
||
TARGET_ATTRIBUTE_VALIDATIONS = { | ||
scale: ->(target_attribute) { validate_types([Fixnum], target_attribute) }, | ||
env_file: ->(target_attribute) { validate_types([String], target_attribute) }, | ||
command: ->(target_attribute) { validate_types([Array, NilClass], target_attribute) }, | ||
predeploy_commands: ->(target_attribute) { validate_predeploy(target_attribute) } | ||
predeploy_commands: ->(target_attribute) { validate_predeploy(target_attribute) }, | ||
service_config: ->(target_attribute) { validate_types([Hash, NilClass], target_attribute) }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
service and task_definition are ECS constructs, so maybe best placed into the ecs_config file instead
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
except that different targets in a file could/will have different service and task configs... if it's in the ECSConfig
won't they have to be unified?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
to be honest i don't really see much point to the EcsConfig
class at all and sort of think all that stuff belongs in the targets.
right now it would be impossible to have a single broadside config file that can deploy to two clusters, as well has having this random extra special config class instead of just a key/value in the targets... and for what?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
originally intended to support things outside of ECS, but whatever. let's just go with this and ditch that config file. This will have to be a major version bump since that will be a breaking change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i don't want to be refactoring at the same time as i'm add features but let's add it to the list of things to be refactored
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'm just going to leave it for now... the breaking change is too much at the moment / i don't feel like repairing configs in a ton of projects.
DE-11289 #time 15m
DE-11289 #time 35m
@@ -92,7 +92,7 @@ def image_tag | |||
end | |||
|
|||
def gen_ssh_cmd(ip, options = { tty: false }) | |||
opts = @deploy_config.ssh | |||
opts = @deploy_config.ssh || {} | |||
cmd = 'ssh -o StrictHostKeyChecking=no' | |||
cmd << ' -t -t' if options[:tty] | |||
cmd << " -i #{opts[:keyfile]}" if opts[:keyfile] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bugfix: if opts[:keyfile]
fails unless opts
is an array
* Debug * debug * debug * debug * Fix bug with container_definition merge * debug * task_definition must come first * Simplify
i added the command and i tested this - deploys still work, so i don't think i broke anything. i was also able to run i fully tested |
scale: 1, | ||
command: ['java', '-cp', '*:.', 'path.to.MyClass'], | ||
env_file: '.env.production', | ||
service: { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the one thing that kind of sucks here is that if you want to just use the defaults to bootstrap, you still have to specify service: {}
and task_definition: {}
(without the keys, and without their values being a hash, you won't be able to trigger a bootstrap).
maybe bootstrap
should just use the defaults if the user doesn't specify any extra config and not check for the existence of service:
and task_definition
?
not the end of the world just kind of blah
Just FYI this feature is done and ready to be reviewed. we're about to start churning out a lot more eventS ETLs quickly so i'd like to lineup the release of this feature in the reasonably but not urgently near future. |
this sounds fine to me but @matl33t has the final word 👍 |
i was just able to use the original script version of this to deploy a new service in about 2 minutes from scratch, which was awesome compared to the old way. |
@@ -166,6 +171,8 @@ on_error do |exception| | |||
error exception.message, "\nRun your last command with --help for more information." | |||
false | |||
else | |||
pp exception.message |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
adding the env var GLI_DEBUG=true
will automatically print out the stacktrace, so don't need this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good to know; i was lost trying to see the stacktrace.
return 'predeploy_commands must be an array' unless target_attribute.is_a?(Array) | ||
|
||
target_attribute.each do |command| | ||
return "predeploy_command '#{command}' must be an array" unless command.is_a?(Array) | ||
messages = target_attribute.select { |cmd| !cmd.is_a?(Array) }.map do |command| |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how about .reject { |cmd| cmd.is_a?(Array) ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sure
@@ -6,33 +7,58 @@ | |||
|
|||
module Broadside | |||
class EcsDeploy < Deploy | |||
|
|||
DEFAULT_DESIRED_COUNT = 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thoughts about putting these constants into the deployconfig class?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's cool with me if it makes sense to have it there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wait you mean in EcsConfig
? the constants are referenced here but that sort of makes sense... not sure it's easier to read if it's in another file
end | ||
end | ||
end | ||
|
||
def bootstrap | ||
unless get_latest_task_def_id | ||
# TODO right now this creates a useless first revision and then update_task_revision will create a 2nd one |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'm fine with this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah it's minor; maybe it's not a TODO it's just a comment
@@ -1,3 +1,3 @@ | |||
module Broadside | |||
VERSION = '1.0.3' | |||
VERSION = '1.1.0' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'll handle the version bump in a separate PR (need to update changelog and whatnot)
just a couple minor comments and i think we are ready to 🚢 ! |
i tested all the commands except the beacon deploy... |
i just used this to actually bootstrap and deploy a new eventS ETL; worked great. |
https://lumoslabs.atlassian.net/browse/DE-11289
this is a proposal based on the script code in https://github.com/lumoslabs/events/pull/103 for how to add the option of setting up services and the initial task_definition from a broadside config that i was playing with a little; figure it might be easier to discuss having it in front of you
@matl33t @rfroetscher @albertrdixon