Skip to content

Instantly share code, notes, and snippets.

@nellshamrell
Last active March 31, 2017 01:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nellshamrell/724282dcb8e768d3fa9a395b63349f4a to your computer and use it in GitHub Desktop.
Save nellshamrell/724282dcb8e768d3fa9a395b63349f4a to your computer and use it in GitHub Desktop.

Starting Out

  • User is on app.habitat.sh
  • User signs in
  • User clicks on "Origins" in side menu
  • User clicks on a specific origin
  • User clicks on "Upload public origin key"

Builder-Web

Code for "Upload public origin key" button

habitat/components/builder-web/origin-page/OriginPageComponent.ts

@Component({
    directives: [KeyAddFormComponent, KeyListComponent,
                 OriginMembersTabComponent, RouterLink, TabsComponent, TabComponent,
                PackagesListComponent],
template: `
(...)
<h3>Public Origin Keys</h3>
                            <p><button
                                *ngIf="iAmPartOfThisOrigin"
                                (click)="setOriginAddingPublicKey(true)"
                                [disabled]="addingPublicKey">
                                Upload public origin key
                            </button></p>

When someone clicks the "Upload public origin key" button, this function fires:

habitat/components/builder-web/origin-page/OriginPageComponent.ts

    private setOriginAddingPublicKey(state: boolean) {
        this.store.dispatch(setCurrentOriginAddingPublicKey(state));
        return false;
}

Which calls:

habitat/components/builder-web/app/actions/origins.ts

export function setCurrentOriginAddingPublicKey(payload: boolean) {
    return {
        type: SET_CURRENT_ORIGIN_ADDING_PUBLIC_KEY,
        payload,
    };
}

That global is then used here:

habitat/components/builder-web/app/reducers/origins.ts

export default function origins(state = initialState["origins"], action) {
switch (action.type) {
(...)
        case actionTypes.SET_CURRENT_ORIGIN_ADDING_PUBLIC_KEY:
            return state.setIn(["ui", "current", "addingPublicKey"],
action.payload);
(...)
    default:
            return state;
}

That "addingPublicKey" state is then used here

habitat/components/builder-web/app/actions/origins.ts

    <hab-key-add-form
                                *ngIf="iAmPartOfThisOrigin && addingPublicKey"
                                [docsUrl]="docsUrl"
                                [errorMessage]="ui.publicKeyErrorMessage"
                                keyFileHeaderPrefix="SIG-PUB-1"
                                [onCloseClick]="onPublicKeyCloseClick"
                                [originName]="origin.name"
                                [uploadKey]="uploadPublicKey">
                            </hab-key-add-form>

The function called when someone submits that form is #uploadPublicKey

habitat/components/builder-web/app/actions/origins.ts

export class OriginPageComponent implements OnInit, OnDestroy {
(...)
    private uploadPublicKey: Function;
(...)
    constructor(private route: ActivatedRoute, private store: AppStore) {
(...)
         this.uploadPublicKey = key =>
            this.store.dispatch(uploadOriginPublicKey(key,
this.gitHubAuthToken));

That calls this function, which calls the BuilderApiClient

habitat/components/builder-web/app/actions/origins.ts

export function uploadOriginPublicKey(key: string, token: string) {
    return dispatch => {
        new BuilderApiClient(token).createOriginKey(key).then(() => {
            dispatch(setOriginPublicKeyUploadErrorMessage(undefined));
            dispatch(setCurrentOriginAddingPublicKey(false));
            dispatch(fetchOriginPublicKeys(parseKey(key).origin, token));
            dispatch(addNotification({
                title: "Origin Public Key Uploaded",
                body: `'${parseKey(key).name}' has been uploaded`,
                type: SUCCESS,
            }));
        }).catch(error => {
            dispatch(setOriginPublicKeyUploadErrorMessage(error.message));
        });
    };
}

Here is the method in the builder api client that POSTs to builder-depot.

    public createOriginKey(key) {
        key = parseKey(key);
        return new Promise((resolve, reject) => {
            fetch(`${this.urlPrefix}/depot/origins/${key.uploadPath}`, {
                body: key.text,
                headers: this.headers,
                method: "POST",
            }).then(response => {
                if  (response.ok) {
                    resolve(true);
                } else {
                    reject(new Error(response.statusText));
                }
            }).catch(error => reject(error));
        });
}

Builder-depot

Here is the route that we just called above

habitat/components/builder-depot/src/server.rs

pub fn router(depot: Depot) -> Result<Chain> {
    let basic = Authenticated::new(&depot.config);
    let worker = Authenticated::new(&depot.config).require(privilege::BUILD_WORKER);
    let router = router!(
(...)
        origin_key_create: post "/origins/:origin/keys/:revision" => {
            if depot.config.insecure {
                XHandler::new(upload_origin_key)
            } else {
                XHandler::new(upload_origin_key).before(basic.clone())
            }
        },

This is the method that route calls. There is a good amount of validations, etc. that go on in this method, but the important part is how it builds up the request, that submits it to through the Broker connection.

habitat/components/builder-depot/src/server.rs

fn upload_origin_key(req: &mut Request) -> IronResult<Response> {
(...)
    let session = req.extensions
        .get::<Authenticated>()
        .unwrap()
        .clone();
    let params = req.extensions
        .get::<Router>()
        .unwrap()
        .clone();
    let mut conn = Broker::connect().unwrap();
    let mut request = OriginPublicKeyCreate::new();
    request.set_owner_id(session.get_id());
    
     let origin = match params.find("origin") {
(...)
        match get_origin(req, origin)? {
           Some(mut origin) => {
                request.set_name(origin.take_name());
                request.set_origin_id(origin.get_id());
(...)
        match params.find("revision") {
            Some(revision) => request.set_revision(revision.to_string()),
            None => return Ok(Response::with(status::BadRequest)),
(...)
    
    request.set_body(key_content);
    request.set_owner_id(0);
        match conn.route::<OriginPublicKeyCreate, OriginPublicKey>(&request) {
        Ok(_) => {
            log_event!(req,
                       Event::OriginKeyUpload {
                           origin: origin.to_string(),
                           version: request.get_revision().to_string(),
                           account: session.get_id().to_string(),
                       });
            let mut response =
                Response::with((status::Created,
                                format!("/origins/{}/keys/{}", &origin, &request.get_revision())));
            let mut base_url: url::Url = req.url.clone().into();
            base_url.set_path(&format!("key/{}-{}", &origin, &request.get_revision()));
            response.headers.set(headers::Location(format!("{}", base_url)));
            Ok(response)
        }
        Err(err) => Ok(render_net_error(&err)),
}

Let's explore some details with that connection.

Net

The code behind the broker is in the net component. The Broker is created as a struct.

habitat/components/net/src/routing.rs

/// A messaging Broker for proxying messages from clients to one or more `RouteSrv` and vice versa.
pub struct Broker {
    client_sock: zmq::Socket,
    router_sock: zmq::Socket,
}

And then several functions are implemented on that struct, including the connect method.

habitat/components/net/src/routing.rs

pub fn connect() -> Result<BrokerConn> {
        let mut conn = try!(BrokerConn::new());
        try!(conn.connect(ROUTE_INPROC_ADDR));
        Ok(conn)
}

Notice that it creates a new BrokerConn instance, here is how that is set up:

habitat/components/net/src/routing.rs

pub struct BrokerConn {
    sock: zmq::Socket,
    hasher: FnvHasher,
}


impl BrokerConn {
    pub fn new() -> Result<Self> {
        let socket = try!((**ZMQ_CONTEXT).as_mut().socket(zmq::REQ));
        try!(socket.set_rcvtimeo(RECV_TIMEOUT_MS));
        try!(socket.set_sndtimeo(SEND_TIMEOUT_MS));
        try!(socket.set_immediate(true));
        Ok(BrokerConn {
               sock: socket,
               hasher: FnvHasher::default(),
           })
    }

So a BrokerConn has a sock attribute and a hasher attribute.

In #upload_origin_key function (used above in builder-depot), we call the #route method on our instance of the BrokerConn and pass it a couple of key arguments.

habitat/components/builder-depot/src/server.rs

match conn.route::<OriginPublicKeyCreate, OriginPublicKey>(&request) 

First is the request type - OriginPublicKeyCreate - this is what will let the router know what origin service to route the request to. And then we also specify what we expect to get back - an OriginPublicKey. And then we also pass on the request that we created.

The route method in the net component is what routes the message to the connected broker, through a router, and to the appropriate service.

It will wait for a response, then parse and return the value of the response.

habitat/components/net/src/routing.rs

pub fn route<M: Routable, R: protobuf::MessageStatic>(&mut self, msg: &M) -> RouteResult<R> {
   if self.route_async(msg).is_err() {
            return Err(protocol::net::err(ErrCode::ZMQ, "net:route:1"));
   }
   match self.recv() {
       Ok(rep) => {
(...)
       match parse_from_bytes::<R>(rep.get_body()) {
             Ok(entity) => Ok(entity),
              Err(err) => {
                 error!("route-recv bad-reply, err={}, reply={:?}", err, rep);
                 Err(protocol::net::err(ErrCode::BUG, "net:route:2"))
              }
}

The first thing the code above does is call the #route_async method. Let's take a closer look at that. This is what actually sends them message.

habitat/components/net/src/routing.rs

    pub fn route_async<M: Routable>(&mut self, msg: &M) -> Result<()> {
        let route_hash = msg.route_key().map(|key| key.hash(&mut self.hasher));
        let req = protocol::Message::new(msg).routing(route_hash).build();
        let bytes = req.write_to_bytes().unwrap();
        try!(self.sock.send_str("RQ", zmq::SNDMORE));
        try!(self.sock.send(&bytes, 0));
        Ok(())
}

After it sends it, it parses the returned message in the #recv function on our instance of BrokerConn. This receives a ZMQ message, then converts it into the real protobuf-defined message construct

pub fn recv(&mut self, flags: i32) -> Result<protocol::net::Msg> {
        let envelope = try!(self.socket.recv_msg(flags));
        let msg: protocol::net::Msg = parse_from_bytes(&envelope).unwrap();
        Ok(msg)
}

Builder-protocol

The message we are sending is OriginPublicKeyCreate. Here is how that message is defined and how we add the Routable trait to it.

habitat/components/builder-protocol/src/message/originsrv.rs

#[derive(PartialEq,Clone,Default)]
pub struct OriginPublicKeyCreate {
    // message fields
    origin_id: ::std::option::Option<u64>,
    name: ::protobuf::SingularField<::std::string::String>,
    revision: ::protobuf::SingularField<::std::string::String>,
    body: ::protobuf::SingularField<::std::vec::Vec<u8>>,
    owner_id: ::std::option::Option<u64>,
    // special fields
    unknown_fields: ::protobuf::UnknownFields,
    cached_size: ::protobuf::CachedSize,
}

// see codegen.rs for the explanation why impl Sync explicitly
unsafe impl ::std::marker::Sync for OriginPublicKeyCreate {}

impl OriginPublicKeyCreate {
    pub fn new() -> OriginPublicKeyCreate {
        ::std::default::Default::default()
    }

    pub fn default_instance() -> &'static OriginPublicKeyCreate {
        static mut instance: ::protobuf::lazy::Lazy<OriginPublicKeyCreate> = ::protobuf::lazy::Lazy {
            lock: ::protobuf::lazy::ONCE_INIT,
            ptr: 0 as *const OriginPublicKeyCreate,
        };
        unsafe {
            instance.get(OriginPublicKeyCreate::new)
        }
    }

    // optional uint64 origin_id = 1;

    pub fn clear_origin_id(&mut self) {
        self.origin_id = ::std::option::Option::None;
    }

    pub fn has_origin_id(&self) -> bool {
        self.origin_id.is_some()
    }

    // Param is passed by value, moved
    pub fn set_origin_id(&mut self, v: u64) {
        self.origin_id = ::std::option::Option::Some(v);
    }

    pub fn get_origin_id(&self) -> u64 {
        self.origin_id.unwrap_or(0)
    }

    fn get_origin_id_for_reflect(&self) -> &::std::option::Option<u64> {
        &self.origin_id
    }

    fn mut_origin_id_for_reflect(&mut self) -> &mut ::std::option::Option<u64> {
        &mut self.origin_id
    }

    // optional string name = 2;

    pub fn clear_name(&mut self) {
        self.name.clear();
    }

    pub fn has_name(&self) -> bool {
        self.name.is_some()
    }

    // Param is passed by value, moved
    pub fn set_name(&mut self, v: ::std::string::String) {
        self.name = ::protobuf::SingularField::some(v);
    }

    // Mutable pointer to the field.
    // If field is not initialized, it is initialized with default value first.
    pub fn mut_name(&mut self) -> &mut ::std::string::String {
        if self.name.is_none() {
            self.name.set_default();
        };
        self.name.as_mut().unwrap()
    }

    // Take field
    pub fn take_name(&mut self) -> ::std::string::String {
        self.name.take().unwrap_or_else(|| ::std::string::String::new())
    }

    pub fn get_name(&self) -> &str {
        match self.name.as_ref() {
            Some(v) => &v,
            None => "",
        }
    }

    fn get_name_for_reflect(&self) -> &::protobuf::SingularField<::std::string::String> {
        &self.name
    }

    fn mut_name_for_reflect(&mut self) -> &mut ::protobuf::SingularField<::std::string::String> {
        &mut self.name
    }

    // optional string revision = 3;

    pub fn clear_revision(&mut self) {
        self.revision.clear();
    }

    pub fn has_revision(&self) -> bool {
        self.revision.is_some()
    }

    // Param is passed by value, moved
    pub fn set_revision(&mut self, v: ::std::string::String) {
        self.revision = ::protobuf::SingularField::some(v);
    }

    // Mutable pointer to the field.
    // If field is not initialized, it is initialized with default value first.
    pub fn mut_revision(&mut self) -> &mut ::std::string::String {
        if self.revision.is_none() {
            self.revision.set_default();
        };
        self.revision.as_mut().unwrap()
    }

    // Take field
    pub fn take_revision(&mut self) -> ::std::string::String {
        self.revision.take().unwrap_or_else(|| ::std::string::String::new())
    }

    pub fn get_revision(&self) -> &str {
        match self.revision.as_ref() {
            Some(v) => &v,
            None => "",
        }
    }

    fn get_revision_for_reflect(&self) -> &::protobuf::SingularField<::std::string::String> {
        &self.revision
    }

    fn mut_revision_for_reflect(&mut self) -> &mut ::protobuf::SingularField<::std::string::String> {
        &mut self.revision
    }

    // optional bytes body = 4;

    pub fn clear_body(&mut self) {
        self.body.clear();
    }

    pub fn has_body(&self) -> bool {
        self.body.is_some()
    }

    // Param is passed by value, moved
    pub fn set_body(&mut self, v: ::std::vec::Vec<u8>) {
        self.body = ::protobuf::SingularField::some(v);
    }

    // Mutable pointer to the field.
    // If field is not initialized, it is initialized with default value first.
    pub fn mut_body(&mut self) -> &mut ::std::vec::Vec<u8> {
        if self.body.is_none() {
            self.body.set_default();
        };
        self.body.as_mut().unwrap()
    }

    // Take field
    pub fn take_body(&mut self) -> ::std::vec::Vec<u8> {
        self.body.take().unwrap_or_else(|| ::std::vec::Vec::new())
    }

    pub fn get_body(&self) -> &[u8] {
        match self.body.as_ref() {
            Some(v) => &v,
            None => &[],
        }
    }

    fn get_body_for_reflect(&self) -> &::protobuf::SingularField<::std::vec::Vec<u8>> {
        &self.body
    }

    fn mut_body_for_reflect(&mut self) -> &mut ::protobuf::SingularField<::std::vec::Vec<u8>> {
        &mut self.body
    }

    // optional uint64 owner_id = 5;

    pub fn clear_owner_id(&mut self) {
        self.owner_id = ::std::option::Option::None;
    }

    pub fn has_owner_id(&self) -> bool {
        self.owner_id.is_some()
    }

    // Param is passed by value, moved
    pub fn set_owner_id(&mut self, v: u64) {
        self.owner_id = ::std::option::Option::Some(v);
    }

    pub fn get_owner_id(&self) -> u64 {
        self.owner_id.unwrap_or(0)
    }

    fn get_owner_id_for_reflect(&self) -> &::std::option::Option<u64> {
        &self.owner_id
    }

    fn mut_owner_id_for_reflect(&mut self) -> &mut ::std::option::Option<u64> {
        &mut self.owner_id
    }
}

components/builder-protocol/src/originsrv.rs

impl Routable for OriginPublicKeyCreate {
    type H = InstaId;

    fn route_key(&self) -> Option<Self::H> {
        Some(InstaId(self.get_owner_id()))
    }
}

Builder-router

Builder OriginSrv

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment