Jekyll2020-08-27T10:40:56+00:00https://pillpall.github.io/feed.xmlBloggy McBlogBlochEnjoy your time!Michael BlochCreate self-signed Certificate with Cloudformation2020-08-27T02:00:00+00:002020-08-27T02:00:00+00:00https://pillpall.github.io/aws/2020/08/27/Create-Self-Signed-Certificate-with-Cloudformation<p>AWS Cloudformation allows you to create self-signed certificates during the stack creation process which you can then use to attach it to other AWS resources like an ELB.</p>
<p>I recently discovered two ways of creating self-signed certificates automated with Cloudformation.</p>
<!--excerpts-->
<h2 id="aws-certificate-manager-private-ca-acmpca">AWS Certificate Manager Private CA (ACMPCA)</h2>
<p>AWS Certificate Manager Private CA(ACMPCA) Service allows you to create and manage a Private CA as easy as it can get. With a Private CA in ACMPCA you are able to create a Self-Signed Certificate in AWS Certificate Manager (ACM).</p>
<p>The integration of ACMPCA in Cloudformation allows you to integrate the creation of a Private CA & the creation of a Self-Signed certificate with ACM into you stack creation process. But keep in mind one Private CA costs around 400$ a month.</p>
<h3 id="create-private-ca">Create Private CA</h3>
<p>The process of creating a Private CA with ACMPCA is as follows</p>
<ul>
<li>Create Internal Root CA</li>
<li>Create Internal Root CA Certificate</li>
<li>Activate internal Root CA</li>
</ul>
<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
</pre></td><td class="code"><pre><span class="na">InternalRootCA</span><span class="pi">:</span>
<span class="na">Type</span><span class="pi">:</span> <span class="s1">'</span><span class="s">AWS::ACMPCA::CertificateAuthority'</span>
<span class="na">Properties</span><span class="pi">:</span>
<span class="na">Type</span><span class="pi">:</span> <span class="s">ROOT</span>
<span class="na">KeyAlgorithm</span><span class="pi">:</span> <span class="s">RSA_2048</span>
<span class="na">SigningAlgorithm</span><span class="pi">:</span> <span class="s">SHA256WITHRSA</span>
<span class="na">Subject</span><span class="pi">:</span>
<span class="na">Country</span><span class="pi">:</span> <span class="s">US</span>
<span class="na">State</span><span class="pi">:</span> <span class="s">California</span>
<span class="na">Locality</span><span class="pi">:</span> <span class="s">San Francisco</span>
<span class="na">Organization</span><span class="pi">:</span> <span class="s">MyOrganization</span>
<span class="na">OrganizationalUnit</span><span class="pi">:</span> <span class="s">MyOrganizationalUnit</span>
<span class="na">CommonName</span><span class="pi">:</span> <span class="s">My own Root CA</span>
<span class="na">SerialNumber</span><span class="pi">:</span> <span class="s1">'</span><span class="s">12345678'</span>
<span class="na">RevocationConfiguration</span><span class="pi">:</span>
<span class="na">CrlConfiguration</span><span class="pi">:</span>
<span class="na">Enabled</span><span class="pi">:</span> <span class="no">false</span>
<span class="na">InternalRootCACertificate</span><span class="pi">:</span>
<span class="na">Type</span><span class="pi">:</span> <span class="s1">'</span><span class="s">AWS::ACMPCA::Certificate'</span>
<span class="na">Properties</span><span class="pi">:</span>
<span class="na">CertificateAuthorityArn</span><span class="pi">:</span> <span class="kt">!Ref</span> <span class="s">InternalRootCA</span>
<span class="na">CertificateSigningRequest</span><span class="pi">:</span> <span class="kt">!GetAtt</span>
<span class="pi">-</span> <span class="s">InternalRootCA</span>
<span class="pi">-</span> <span class="s">CertificateSigningRequest</span>
<span class="na">SigningAlgorithm</span><span class="pi">:</span> <span class="s">SHA256WITHRSA</span>
<span class="na">TemplateArn</span><span class="pi">:</span> <span class="s1">'</span><span class="s">arn:aws:acm-pca:::template/RootCACertificate/V1'</span>
<span class="na">Validity</span><span class="pi">:</span>
<span class="na">Type</span><span class="pi">:</span> <span class="s">YEARS</span>
<span class="na">Value</span><span class="pi">:</span> <span class="m">5</span>
<span class="na">InternalRootCAActivation</span><span class="pi">:</span>
<span class="na">Type</span><span class="pi">:</span> <span class="s1">'</span><span class="s">AWS::ACMPCA::CertificateAuthorityActivation'</span>
<span class="na">Properties</span><span class="pi">:</span>
<span class="na">CertificateAuthorityArn</span><span class="pi">:</span> <span class="kt">!Ref</span> <span class="s">InternalRootCA</span>
<span class="na">Certificate</span><span class="pi">:</span> <span class="kt">!GetAtt</span>
<span class="pi">-</span> <span class="s">InternalRootCACertificate</span>
<span class="pi">-</span> <span class="s">Certificate</span>
<span class="na">Status</span><span class="pi">:</span> <span class="s">ACTIVE</span>
</pre></td></tr></tbody></table></code></pre></figure>
<h3 id="create-self-signed-certificate-in-acm">Create Self-Signed Certificate in ACM</h3>
<p>To create your Self-Signed Certificate in ACM you need to wait until the Root CA was activated & you need the ARN of the created Root CA.</p>
<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
</pre></td><td class="code"><pre><span class="na">ACMInternalCertificate</span><span class="pi">:</span>
<span class="na">DependsOn</span><span class="pi">:</span> <span class="s">InternalRootCAActivation</span>
<span class="na">Type</span><span class="pi">:</span> <span class="s">AWS::CertificateManager::Certificate</span>
<span class="na">Properties</span><span class="pi">:</span>
<span class="na">CertificateAuthorityArn</span><span class="pi">:</span> <span class="kt">!Ref</span> <span class="s">InternalRootCA</span>
<span class="na">DomainName</span><span class="pi">:</span> <span class="s1">'</span><span class="s">example.com'</span>
<span class="na">SubjectAlternativeNames</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s2">"</span><span class="s">*.example.com"</span>
</pre></td></tr></tbody></table></code></pre></figure>
<p>is and the other one is to use a Python Lambda function to create a self-signed certificate and upload it to AWS Certificate Manager.</p>
<h2 id="lambda-and-custom-resource">Lambda and Custom Resource</h2>
<p>If you don’t want to create your Private CA in ACMPCA you can use Lambda and Custom Resources to create a Self-Signed Certificate and store it in ACM. The Python module <code class="language-plaintext highlighter-rouge">pyopenssl</code> allows you to create a Private CA & Self-Signed Certificate with Python.</p>
<figure class="highlight"><pre><code class="language-python" data-lang="python"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
</pre></td><td class="code"><pre><span class="kn">import</span> <span class="nn">random</span>
<span class="kn">from</span> <span class="nn">OpenSSL</span> <span class="kn">import</span> <span class="n">crypto</span>
<span class="c1"># Create CA
</span><span class="n">ca_key</span> <span class="o">=</span> <span class="n">crypto</span><span class="p">.</span><span class="n">PKey</span><span class="p">()</span>
<span class="n">ca_key</span><span class="p">.</span><span class="n">generate_key</span><span class="p">(</span><span class="n">crypto</span><span class="p">.</span><span class="n">TYPE_RSA</span><span class="p">,</span> <span class="mi">2048</span><span class="p">)</span>
<span class="n">ca_cert</span> <span class="o">=</span> <span class="n">crypto</span><span class="p">.</span><span class="n">X509</span><span class="p">()</span>
<span class="n">ca_cert</span><span class="p">.</span><span class="n">set_version</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span>
<span class="n">ca_cert</span><span class="p">.</span><span class="n">set_serial_number</span><span class="p">(</span><span class="n">random</span><span class="p">.</span><span class="n">randrange</span><span class="p">(</span><span class="mi">100000</span><span class="p">))</span>
<span class="n">ca_subj</span> <span class="o">=</span> <span class="n">ca_cert</span><span class="p">.</span><span class="n">get_subject</span><span class="p">()</span>
<span class="n">ca_subj</span><span class="p">.</span><span class="n">C</span> <span class="o">=</span> <span class="s">"US"</span>
<span class="n">ca_subj</span><span class="p">.</span><span class="n">ST</span> <span class="o">=</span> <span class="s">"California"</span>
<span class="n">ca_subj</span><span class="p">.</span><span class="n">L</span> <span class="o">=</span> <span class="s">"San Francisco"</span>
<span class="n">ca_subj</span><span class="p">.</span><span class="n">O</span> <span class="o">=</span> <span class="s">"MyOrganization"</span>
<span class="n">ca_subj</span><span class="p">.</span><span class="n">OU</span> <span class="o">=</span> <span class="s">"MyOrganizationalUnit"</span>
<span class="n">ca_subj</span><span class="p">.</span><span class="n">CN</span> <span class="o">=</span> <span class="s">"My own Root CA"</span>
<span class="n">ca_cert</span><span class="p">.</span><span class="n">add_extensions</span><span class="p">([</span>
<span class="n">crypto</span><span class="p">.</span><span class="n">X509Extension</span><span class="p">(</span><span class="s">b"subjectKeyIdentifier"</span><span class="p">,</span> <span class="bp">False</span><span class="p">,</span> <span class="s">b"hash"</span><span class="p">,</span> <span class="n">subject</span><span class="o">=</span><span class="n">ca_cert</span><span class="p">),</span>
<span class="p">])</span>
<span class="n">ca_cert</span><span class="p">.</span><span class="n">add_extensions</span><span class="p">([</span>
<span class="n">crypto</span><span class="p">.</span><span class="n">X509Extension</span><span class="p">(</span><span class="s">b"authorityKeyIdentifier"</span><span class="p">,</span> <span class="bp">False</span><span class="p">,</span> <span class="s">b"keyid:always"</span><span class="p">,</span> <span class="n">issuer</span><span class="o">=</span><span class="n">ca_cert</span><span class="p">),</span>
<span class="p">])</span>
<span class="n">ca_cert</span><span class="p">.</span><span class="n">add_extensions</span><span class="p">([</span>
<span class="n">crypto</span><span class="p">.</span><span class="n">X509Extension</span><span class="p">(</span><span class="s">b"basicConstraints"</span><span class="p">,</span> <span class="bp">False</span><span class="p">,</span> <span class="s">b"CA:TRUE"</span><span class="p">),</span>
<span class="n">crypto</span><span class="p">.</span><span class="n">X509Extension</span><span class="p">(</span><span class="s">b"keyUsage"</span><span class="p">,</span> <span class="bp">False</span><span class="p">,</span> <span class="s">b"keyCertSign, cRLSign"</span><span class="p">),</span>
<span class="p">])</span>
<span class="n">ca_cert</span><span class="p">.</span><span class="n">set_issuer</span><span class="p">(</span><span class="n">ca_subj</span><span class="p">)</span>
<span class="n">ca_cert</span><span class="p">.</span><span class="n">set_pubkey</span><span class="p">(</span><span class="n">ca_key</span><span class="p">)</span>
<span class="n">ca_cert</span><span class="p">.</span><span class="n">sign</span><span class="p">(</span><span class="n">ca_key</span><span class="p">,</span> <span class="s">'sha256'</span><span class="p">)</span>
<span class="n">ca_cert</span><span class="p">.</span><span class="n">gmtime_adj_notBefore</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="n">ca_cert</span><span class="p">.</span><span class="n">gmtime_adj_notAfter</span><span class="p">(</span><span class="mi">10</span><span class="o">*</span><span class="mi">365</span><span class="o">*</span><span class="mi">24</span><span class="o">*</span><span class="mi">60</span><span class="o">*</span><span class="mi">60</span><span class="p">)</span>
<span class="n">ca_certifictate_pem</span> <span class="o">=</span> <span class="n">crypto</span><span class="p">.</span><span class="n">dump_certificate</span><span class="p">(</span><span class="n">crypto</span><span class="p">.</span><span class="n">FILETYPE_PEM</span><span class="p">,</span> <span class="n">ca_cert</span><span class="p">).</span><span class="n">decode</span><span class="p">(</span><span class="s">'utf-8'</span><span class="p">)</span>
<span class="c1"># Create Self-Signed Certificate
</span><span class="n">client_key</span> <span class="o">=</span> <span class="n">crypto</span><span class="p">.</span><span class="n">PKey</span><span class="p">()</span>
<span class="n">client_key</span><span class="p">.</span><span class="n">generate_key</span><span class="p">(</span><span class="n">crypto</span><span class="p">.</span><span class="n">TYPE_RSA</span><span class="p">,</span> <span class="mi">2048</span><span class="p">)</span>
<span class="n">client_cert</span> <span class="o">=</span> <span class="n">crypto</span><span class="p">.</span><span class="n">X509</span><span class="p">()</span>
<span class="n">client_cert</span><span class="p">.</span><span class="n">set_version</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span>
<span class="n">client_cert</span><span class="p">.</span><span class="n">set_serial_number</span><span class="p">(</span><span class="n">random</span><span class="p">.</span><span class="n">randrange</span><span class="p">(</span><span class="mi">100000</span><span class="p">))</span>
<span class="n">client_subj</span> <span class="o">=</span> <span class="n">client_cert</span><span class="p">.</span><span class="n">get_subject</span><span class="p">()</span>
<span class="n">client_subj</span><span class="p">.</span><span class="n">C</span> <span class="o">=</span> <span class="s">"US"</span>
<span class="n">client_subj</span><span class="p">.</span><span class="n">ST</span> <span class="o">=</span> <span class="s">"California"</span>
<span class="n">client_subj</span><span class="p">.</span><span class="n">L</span> <span class="o">=</span> <span class="s">"San Francisco"</span>
<span class="n">client_subj</span><span class="p">.</span><span class="n">O</span> <span class="o">=</span> <span class="s">"MyOrganization"</span>
<span class="n">client_subj</span><span class="p">.</span><span class="n">OU</span> <span class="o">=</span> <span class="s">"MyOrganizationalUnit"</span>
<span class="n">client_subj</span><span class="p">.</span><span class="n">CN</span> <span class="o">=</span> <span class="s">"example.com"</span>
<span class="n">client_cert</span><span class="p">.</span><span class="n">add_extensions</span><span class="p">([</span>
<span class="n">crypto</span><span class="p">.</span><span class="n">X509Extension</span><span class="p">(</span><span class="s">b"basicConstraints"</span><span class="p">,</span> <span class="bp">False</span><span class="p">,</span> <span class="s">b"CA:FALSE"</span><span class="p">),</span>
<span class="n">crypto</span><span class="p">.</span><span class="n">X509Extension</span><span class="p">(</span><span class="s">b"subjectKeyIdentifier"</span><span class="p">,</span> <span class="bp">False</span><span class="p">,</span> <span class="s">b"hash"</span><span class="p">,</span> <span class="n">subject</span><span class="o">=</span><span class="n">client_cert</span><span class="p">),</span>
<span class="p">])</span>
<span class="n">client_cert</span><span class="p">.</span><span class="n">add_extensions</span><span class="p">([</span>
<span class="n">crypto</span><span class="p">.</span><span class="n">X509Extension</span><span class="p">(</span><span class="s">b"authorityKeyIdentifier"</span><span class="p">,</span> <span class="bp">False</span><span class="p">,</span> <span class="s">b"keyid:always"</span><span class="p">,</span> <span class="n">issuer</span><span class="o">=</span><span class="n">ca_cert</span><span class="p">),</span>
<span class="n">crypto</span><span class="p">.</span><span class="n">X509Extension</span><span class="p">(</span><span class="s">b"extendedKeyUsage"</span><span class="p">,</span> <span class="bp">False</span><span class="p">,</span> <span class="s">b"serverAuth"</span><span class="p">),</span>
<span class="n">crypto</span><span class="p">.</span><span class="n">X509Extension</span><span class="p">(</span><span class="s">b"keyUsage"</span><span class="p">,</span> <span class="bp">False</span><span class="p">,</span> <span class="s">b"digitalSignature"</span><span class="p">),</span>
<span class="p">])</span>
<span class="n">client_cert</span><span class="p">.</span><span class="n">add_extensions</span><span class="p">([</span>
<span class="n">crypto</span><span class="p">.</span><span class="n">X509Extension</span><span class="p">(</span><span class="s">b'subjectAltName'</span><span class="p">,</span> <span class="bp">False</span><span class="p">,</span>
<span class="s">','</span><span class="p">.</span><span class="n">join</span><span class="p">([</span>
<span class="s">'DNS:*.example.com'</span>
<span class="p">]).</span><span class="n">encode</span><span class="p">())])</span>
<span class="n">client_cert</span><span class="p">.</span><span class="n">set_issuer</span><span class="p">(</span><span class="n">ca_subj</span><span class="p">)</span>
<span class="n">client_cert</span><span class="p">.</span><span class="n">set_pubkey</span><span class="p">(</span><span class="n">client_key</span><span class="p">)</span>
<span class="n">client_cert</span><span class="p">.</span><span class="n">gmtime_adj_notBefore</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="n">client_cert</span><span class="p">.</span><span class="n">gmtime_adj_notAfter</span><span class="p">(</span><span class="mi">9</span><span class="o">*</span><span class="mi">365</span><span class="o">*</span><span class="mi">24</span><span class="o">*</span><span class="mi">60</span><span class="o">*</span><span class="mi">60</span><span class="p">)</span>
<span class="n">client_cert</span><span class="p">.</span><span class="n">sign</span><span class="p">(</span><span class="n">ca_key</span><span class="p">,</span> <span class="s">'sha256'</span><span class="p">)</span>
<span class="n">certifictate_pem</span> <span class="o">=</span> <span class="n">crypto</span><span class="p">.</span><span class="n">dump_certificate</span><span class="p">(</span><span class="n">crypto</span><span class="p">.</span><span class="n">FILETYPE_PEM</span><span class="p">,</span> <span class="n">client_cert</span><span class="p">).</span><span class="n">decode</span><span class="p">(</span><span class="s">'utf-8'</span><span class="p">)</span>
<span class="n">private_key_pem</span> <span class="o">=</span> <span class="n">crypto</span><span class="p">.</span><span class="n">dump_privatekey</span><span class="p">(</span><span class="n">crypto</span><span class="p">.</span><span class="n">FILETYPE_PEM</span><span class="p">,</span> <span class="n">client_key</span><span class="p">).</span><span class="n">decode</span><span class="p">(</span><span class="s">'utf-8'</span><span class="p">)</span>
<span class="n">response</span> <span class="o">=</span> <span class="p">{</span>
<span class="s">'certificate'</span><span class="p">:</span> <span class="n">certifictate_pem</span><span class="p">,</span>
<span class="s">'private_key'</span><span class="p">:</span> <span class="n">private_key_pem</span><span class="p">,</span>
<span class="s">'certificate_chain'</span><span class="p">:</span> <span class="n">ca_certifictate_pem</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></figure>
<p>Now that you have the self-signed certificate, private key & certificate chain you can use boto to upload it to ACM.</p>
<figure class="highlight"><pre><code class="language-python" data-lang="python"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
</pre></td><td class="code"><pre><span class="kn">import</span> <span class="nn">boto3</span>
<span class="n">acm</span> <span class="o">=</span> <span class="n">boto3</span><span class="p">.</span><span class="n">client</span><span class="p">(</span><span class="s">'acm'</span><span class="p">)</span>
<span class="n">response</span> <span class="o">=</span> <span class="n">acm</span><span class="p">.</span><span class="n">import_certificate</span><span class="p">(</span>
<span class="n">Certificate</span><span class="o">=</span><span class="n">certificate</span><span class="p">,</span>
<span class="n">PrivateKey</span><span class="o">=</span><span class="n">private_key</span><span class="p">,</span>
<span class="n">CertificateChain</span><span class="o">=</span><span class="n">certificate_chain</span><span class="p">,</span>
<span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></figure>Michael BlochAWS Cloudformation allows you to create self-signed certificates during the stack creation process which you can then use to attach it to other AWS resources like an ELB. I recently discovered two ways of creating self-signed certificates automated with Cloudformation.How to use puppet to check if a file / directory exists2019-10-10T02:00:00+00:002019-10-10T02:00:00+00:00https://pillpall.github.io/puppet/2019/10/10/How-to-check-if-a-file-path-exists-with-puppet<p>I searched for a very long time an easy way to use exists conditions on files and directories. I recently discovered that you can use the built-in function <code class="language-plaintext highlighter-rouge">find_file</code> for this.</p>
<!--excerpts-->
<p>The built-in function <code class="language-plaintext highlighter-rouge">find_file</code> allows you to check if a certain file or a directory on the operating system exists. If it exists puppet will response with a <code class="language-plaintext highlighter-rouge">string</code> containing the path to the file or directory. If it does not exists puppet will response with an <code class="language-plaintext highlighter-rouge">undef</code> data type. This allows us to use this in if/else conditions.</p>
<h2 id="how-to-use-puppet-to-check-if-a-file-exists-">How to use puppet to check if a file exists ?</h2>
<p>This is a simple file existence check:</p>
<figure class="highlight"><pre><code class="language-puppet" data-lang="puppet"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
</pre></td><td class="code"><pre><span class="nv">$file_path</span> <span class="o">=</span> <span class="s1">'/tmp/test_file'</span>
<span class="nv">$file_exists</span> <span class="o">=</span> <span class="nf">find_file</span><span class="p">(</span><span class="nv">$file_path</span><span class="p">)</span>
<span class="k">if</span> <span class="nv">$file_exists</span> <span class="p">{</span>
<span class="n">notify</span><span class="p">{</span><span class="s2">"File </span><span class="nv">${file_path}</span><span class="s2"> exist"</span><span class="p">:}</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="n">notify</span><span class="p">{</span><span class="s2">"File </span><span class="nv">${file_path}</span><span class="s2"> does not exist"</span><span class="p">:}</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></figure>
<h2 id="how-to-use-puppet-to-check-if-a-directory-exists-">How to use puppet to check if a directory exists ?</h2>
<p>This is a simple directory existence check:</p>
<figure class="highlight"><pre><code class="language-puppet" data-lang="puppet"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
</pre></td><td class="code"><pre><span class="nv">$dir_path</span> <span class="o">=</span> <span class="s1">'/tmp/test_path'</span>
<span class="nv">$path_exists</span> <span class="o">=</span> <span class="nf">find_file</span><span class="p">(</span><span class="nv">$dir_path</span><span class="p">)</span>
<span class="k">if</span> <span class="nv">$path_exists</span> <span class="p">{</span>
<span class="n">notify</span><span class="p">{</span><span class="s2">"Path </span><span class="nv">${dir_path}</span><span class="s2"> exist"</span><span class="p">:}</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="n">notify</span><span class="p">{</span><span class="s2">"Path </span><span class="nv">${dir_path}</span><span class="s2"> does not exist"</span><span class="p">:}</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></figure>
<p>As you can see the usage for a file and a directory is the same.</p>
<p>You can find more informations about Puppets built-in function and the built-in function <code class="language-plaintext highlighter-rouge">find_file</code> in the puppet documentation <a href="https://puppet.com/docs/puppet/5.5/function.html">Link</a>.</p>
<p>Cheers</p>Michael BlochI searched for a very long time an easy way to use exists conditions on files and directories. I recently discovered that you can use the built-in function find_file for this.Using variables to Call Puppet resources2019-05-13T02:00:00+00:002019-05-13T02:00:00+00:00https://pillpall.github.io/puppet/2019/05/13/Using-variables-to-Call-Puppet-resources<p>Sometimes you may wish to use variables for calling a specific Puppet resources instead of using if-else conditions. Luckily Puppet allows us to use variables in Puppet resource names like classes, modules etc. …</p>
<!--excerpts-->
<p>In my case I had several Puppet classes like <code class="language-plaintext highlighter-rouge">aem_curator::install_aem62</code>, <code class="language-plaintext highlighter-rouge">aem_curator::install_aem63</code> and <code class="language-plaintext highlighter-rouge">aem_curator::install_aem64</code>. To call the right Puppet class for the installation I used the following If-else conditions:</p>
<figure class="highlight"><pre><code class="language-puppet" data-lang="puppet"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
</pre></td><td class="code"><pre><span class="k">if</span> <span class="nv">$aem_profile</span> <span class="o">==</span> <span class="s1">'aem62'</span> <span class="p">{</span>
<span class="n">aem_curator::install_aem62</span> <span class="p">{</span> <span class="s2">"</span><span class="nv">${aem_id}</span><span class="s2">: Install AEM profile </span><span class="nv">${aem_profile}</span><span class="s2">"</span><span class="p">:</span>
<span class="py">aem_artifacts_base</span> <span class="p">=></span> <span class="nv">$aem_artifacts_base</span><span class="p">,</span>
<span class="py">aem_base</span> <span class="p">=></span> <span class="nv">$aem_base</span><span class="p">,</span>
<span class="p">}</span>
<span class="p">}</span> <span class="k">elsif</span> <span class="nv">$aem_profile</span> <span class="o">==</span> <span class="s1">'aem63'</span> <span class="p">{</span>
<span class="n">aem_curator::install_aem63</span> <span class="p">{</span> <span class="s2">"</span><span class="nv">${aem_id}</span><span class="s2">: Install AEM profile </span><span class="nv">${aem_profile}</span><span class="s2">"</span><span class="p">:</span>
<span class="py">aem_artifacts_base</span> <span class="p">=></span> <span class="nv">$aem_artifacts_base</span><span class="p">,</span>
<span class="py">aem_base</span> <span class="p">=></span> <span class="nv">$aem_base</span><span class="p">,</span>
<span class="p">}</span>
<span class="p">}</span> <span class="k">elsif</span> <span class="nv">$aem_profile</span> <span class="o">==</span> <span class="s1">'aem_64'</span> <span class="p">{</span>
<span class="n">aem_curator::install_aem64</span> <span class="p">{</span> <span class="s2">"</span><span class="nv">${aem_id}</span><span class="s2">: Install AEM profile </span><span class="nv">${aem_profile}</span><span class="s2">"</span><span class="p">:</span>
<span class="py">aem_artifacts_base</span> <span class="p">=></span> <span class="nv">$aem_artifacts_base</span><span class="p">,</span>
<span class="py">aem_base</span> <span class="p">=></span> <span class="nv">$aem_base</span><span class="p">,</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></figure>
<p>As you can see this file can be getting to big to keep track of it very easily.</p>
<p>So instead of maintaining such a complex Puppet manifest I want to reduce it to one Puppet resource call by calling a class using the variable <code class="language-plaintext highlighter-rouge">aem_profile</code> in the class name. To do so we can call a Puppet resource within <code class="language-plaintext highlighter-rouge">Resource[]</code>.</p>
<figure class="highlight"><pre><code class="language-puppet" data-lang="puppet"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="code"><pre><span class="nc">Resource</span><span class="p">[</span><span class="s2">"aem_curator::install_</span><span class="nv">${aem_profile}</span><span class="s2">"</span><span class="p">]</span> <span class="p">{</span> <span class="s2">"</span><span class="nv">${aem_id}</span><span class="s2">: Install AEM profile </span><span class="nv">${aem_profile}</span><span class="s2">"</span><span class="p">:</span>
<span class="py">aem_artifacts_base</span> <span class="p">=></span> <span class="nv">$aem_artifacts_base</span><span class="p">,</span>
<span class="py">aem_base</span> <span class="p">=></span> <span class="nv">$aem_base</span><span class="p">,</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></figure>
<p>As you can see, the class call now depends on the variable <code class="language-plaintext highlighter-rouge">${aem_profile}</code>. This variable defines which Puppet class we want to call.</p>
<p>In my case I reduced a manifest of 280 lines down to 40 lines <a href="https://github.com/shinesolutions/puppet-aem-curator/commit/87b303f39759b49a308f4e60c9d700cbfbaa8554#diff-fcd29db3229524a51a7f2645715cdaec">Example</a>.</p>
<p>Cheers</p>Michael BlochSometimes you may wish to use variables for calling a specific Puppet resources instead of using if-else conditions. Luckily Puppet allows us to use variables in Puppet resource names like classes, modules etc. …Google Gsuite as SAML provider in AEM2019-05-05T02:00:00+00:002019-05-05T02:00:00+00:00https://pillpall.github.io/aem/2019/05/05/Google-Gsuite-as-SAML-provider-in-AEM<p>Just recently I had to test my SAML automation for Adobe Experience Manager and faced the problem that I couldn’t test it because I don’t have a running SAML server I can use for testing. But luckily I found out how I can configure Google Suite as SAML provider in AEM.</p>
<!--excerpts-->
<p>First thing, for more information about SAML in AEM I recommend you this blog post about <a href="https://shinesolutions.com/2018/12/03/sso-with-saml-authentication-using-shibboleth-idp/">SAML and how to configure SAML in AEM</a>.</p>
<p>The steps to configure Google Gsuite as SAML provider in AEM are as follows:</p>
<ul>
<li>Create new SAML Application in Gsuite</li>
<li>Configure Gsuite as SAML provider in AEM</li>
</ul>
<h2 id="create-new-saml-app-in-google-gsuite">Create new SAML App in Google Gsuite</h2>
<p>To create a new SAML app in Google Gsuite login to your Gsuite Admin account and create a new custom SAML app</p>
<ul>
<li>Copy <code class="language-plaintext highlighter-rouge">SSO URL</code></li>
<li>Download Certificate</li>
<li><code class="language-plaintext highlighter-rouge">ACS URL</code> e.g. <code class="language-plaintext highlighter-rouge">https://author.aem-opencloud.com:5432/saml_login</code></li>
<li><code class="language-plaintext highlighter-rouge">Entity ID</code> e.g. <code class="language-plaintext highlighter-rouge">AEMSSO</code></li>
<li>Attribute mapping:</li>
</ul>
<table>
<thead>
<tr>
<th>Application Attribute</th>
<th>Information</th>
<th>User attribute</th>
</tr>
</thead>
<tbody>
<tr>
<td>mail</td>
<td>Basic Information</td>
<td>Primary Email</td>
</tr>
<tr>
<td>givenName</td>
<td>Basic Information</td>
<td>First Name</td>
</tr>
<tr>
<td>familyName</td>
<td>Basic Information</td>
<td>Last Name</td>
</tr>
</tbody>
</table>
<p>Now that we configured our SAML application in Google Gsuite we can configure SAML in AEM.</p>
<h2 id="configure-gsuite-as-saml-provider-in-aem">Configure Gsuite as SAML provider in AEM</h2>
<p>For configuring Gsuite as SAML provider in AEM</p>
<ul>
<li>Upload certificate to the AEM Global Truststore.</li>
<li>Configure <strong>Adobe Granite SAML 2.0 Authentication Handler</strong> with the following options:</li>
</ul>
<p>path: <code class="language-plaintext highlighter-rouge">/</code></p>
<p>service.ranking: <code class="language-plaintext highlighter-rouge">5002</code></p>
<p>idpUrl: the copied <strong>SSO URL</strong> e.g. <code class="language-plaintext highlighter-rouge">https://accounts.google.com/o/saml2/idp?idpid=C03abcde1f</code></p>
<p>idpCertAlias: Certificate aliase name from the Global Truststore</p>
<p>Now that Google Gsuite is successfully configured as SAML provider in AEM you can try to login with your google Account to AEM.</p>
<p>Cheers</p>Michael BlochJust recently I had to test my SAML automation for Adobe Experience Manager and faced the problem that I couldn’t test it because I don’t have a running SAML server I can use for testing. But luckily I found out how I can configure Google Suite as SAML provider in AEM.Find cURL parameters for AEM2018-06-17T02:00:00+00:002018-06-17T02:00:00+00:00https://pillpall.github.io/aem/2018/06/17/Find-CURL-parameters-for-AEM<p>To determine which URL and parameters we need to manage AEM via cURL or similar(Ruby/Python) we simply do the steps manually via a browser while using the <strong>Web-Developer tool Network</strong> from the browser or we use something like <strong>tcpdump</strong> or <strong>wireshark</strong>.</p>
<!--excerpts-->
<h2 id="example-create-a-truststore">Example create a truststore</h2>
<p>To find out what we need to create a Truststore in AEM via cURL we need to do the following steps.</p>
<ul>
<li>Browse to http://localhost:4502/libs/granite/security/content/useradmin.html</li>
<li>Edit a User</li>
<li>Open Web-Developer tool Network in Firefox</li>
<li>Under <strong>Account settings</strong> click on <strong>Create TrustStore</strong></li>
</ul>
<p>Now you will see in the Network tool a <strong>POST</strong> call/method:
<img src="https://pillpall.github.io/assets/posts/aem_cURL/1_network_console.png" alt="Network Console" /></p>
<p>When you click on it you will see the <strong>request url</strong> and the <strong>request method</strong>. Which gives us the following cURL command so far:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl -u 'admin:admin' -X POST http://localhost:4502/libs/granite/security/post/truststore
</code></pre></div></div>
<p>To know which options we need to add to our cURL command we need to click on <strong>Edit and Resend</strong>. In the <strong>request body</strong> we will see all options we need to add to our cURL command.
<img src="https://pillpall.github.io/assets/posts/aem_cURL/2_new_request.png" alt="Request Body" /></p>
<p>The request body contains the parameter we need to add to the cURL command. It will look something similar to</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>newPassword=admin&rePassword=admin&%3Aoperation=createStore&%3Acq_csrf_token=eyJleHAiOjE1MjkyMzM2MzMsImlhdCI6asdfUyOTIzMzAzM30.TnWwTuYva_kpoLnS0x_Y_asdfAQNuOwYMVCE
</code></pre></div></div>
<p>We can delete everything after <strong>&%3Acq_csrf_token</strong> as we do not provide a authentication token which will give us the following request body</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>newPassword=admin&rePassword=admin&%3Aoperation=createStore
</code></pre></div></div>
<p>Now we have all Parameters we need to know to create a Truststore via cURL <strong>newPassword</strong>, <strong>rePassword</strong> & <strong>:operation</strong>. Our cURL command to create a Truststore is as follows:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl -u 'admin:admin' -X POST 'http://localhost:4502/libs/granite/security/post/truststore?newPassword=admin&rePassword=admin&%3Aoperation=createStore'
</code></pre></div></div>
<p>or:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl -u 'admin:admin' -F 'newPassword=admin' -F 'rePassword=admin' -F ':operation=createStore' http://localhost:4502/libs/granite/security/post/truststore
</code></pre></div></div>
<p>Following these steps you may be able to create your own cURL command.</p>
<p>Cheers!</p>Michael BlochTo determine which URL and parameters we need to manage AEM via cURL or similar(Ruby/Python) we simply do the steps manually via a browser while using the Web-Developer tool Network from the browser or we use something like tcpdump or wireshark.Use Ansible to automate Spotify - Part II2018-05-14T02:00:00+00:002018-05-14T02:00:00+00:00https://pillpall.github.io/ansible/2018/05/14/Use-Ansible-to-automate-Spotify-part-II<p>The last time I wrote a small introduction about how to use the Spotify authentication modules to create an <strong>authentication token</strong> and how this authentication token can be consumed by other Spotify Ansible modules e.g. <code class="language-plaintext highlighter-rouge">spotify_related_artists</code> (link)[/ansible/2018/04/28/Use-Ansible-to-automate-Spotify-part-I.html]. This time I am going to show you several use cases which combine several modules/roles.</p>
<ul>
<li>Create a playlist for a specific album</li>
<li>Add related artists top tracks to a new playlist</li>
<li>Play a specific playlist on a specific device</li>
</ul>
<!--excerpts-->
<h2 id="create-a-playlist-for-a-specific-album">Create a playlist for a specific album</h2>
<p>Let’s start with something easy. We want to search for an album and add all tracks to a new playlist. To realise this we need the following Ansible modules:</p>
<ul>
<li>spotify_auth</li>
<li>spotify_auth_create_user_token</li>
<li>spotify_search</li>
<li>spotify_album</li>
<li>spotify_user_playlists</li>
<li>spotify_update_playlists</li>
</ul>
<p>or the roles:</p>
<ul>
<li>authenticaton</li>
<li>search</li>
<li>album</li>
<li>user_playlists</li>
<li>update_playlists</li>
</ul>
<p><strong>The Playbook for using the roles may look like this:</strong></p>
<p>Task 1: Create authentication token</p>
<p>Task 2: Search for album <em>Tenacious D</em> and response the first album you find</p>
<p>Task 3: Get all tracks from the album</p>
<p>Task 4: Create a new user playlist with the name <em>Tenacious D</em></p>
<p>Task 5: Add all album tracks to the new playlist</p>
<p><img src="https://i.imgur.com/8XZYASU.gif" alt="Create album playlist" /></p>
<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
</pre></td><td class="code"><pre><span class="nn">---</span>
<span class="pi">-</span> <span class="na">hosts</span><span class="pi">:</span> <span class="s">localhost</span>
<span class="na">connection</span><span class="pi">:</span> <span class="s">local</span>
<span class="na">gather_facts</span><span class="pi">:</span> <span class="no">false</span>
<span class="na">vars</span><span class="pi">:</span>
<span class="na">temp_dest_file</span><span class="pi">:</span> <span class="s2">"</span><span class="s">/tmp/tempfile.json"</span>
<span class="na">temp_playlist_file</span><span class="pi">:</span> <span class="s2">"</span><span class="s">/tmp/temp_playlist.json"</span>
<span class="na">username</span><span class="pi">:</span> <span class="s">bloch-m</span>
<span class="na">tasks</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Create user authentication token</span>
<span class="na">include_role</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">authentication</span>
<span class="na">vars</span><span class="pi">:</span>
<span class="na">config_file</span><span class="pi">:</span> <span class="s2">"</span><span class="s">{{inventory_dir}}/group_vars/user.yaml"</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">search for album</span>
<span class="na">include_role</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">search</span>
<span class="na">vars</span><span class="pi">:</span>
<span class="na">search_for</span><span class="pi">:</span> <span class="s">albums</span>
<span class="na">search_for_albums_name</span><span class="pi">:</span> <span class="s">Tenacious D</span>
<span class="na">search_result_output</span><span class="pi">:</span> <span class="s">short</span>
<span class="na">search_result_limit</span><span class="pi">:</span> <span class="m">1</span>
<span class="na">search_dest_file</span><span class="pi">:</span> <span class="s2">"</span><span class="s">{{</span><span class="nv"> </span><span class="s">temp_dest_file</span><span class="nv"> </span><span class="s">}}"</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Get album informations</span>
<span class="na">include_role</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">album</span>
<span class="na">vars</span><span class="pi">:</span>
<span class="na">spotify_album_file</span><span class="pi">:</span> <span class="s2">"</span><span class="s">{{</span><span class="nv"> </span><span class="s">temp_dest_file</span><span class="nv"> </span><span class="s">}}"</span>
<span class="na">spotify_album_dest_file</span><span class="pi">:</span> <span class="s2">"</span><span class="s">{{</span><span class="nv"> </span><span class="s">temp_dest_file</span><span class="nv"> </span><span class="s">}}"</span>
<span class="na">spotify_album_output_format</span><span class="pi">:</span> <span class="s">short</span>
<span class="na">spotify_album_state</span><span class="pi">:</span> <span class="s">album_tracks</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Create user playlist</span>
<span class="na">include_role</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">user_playlists</span>
<span class="na">vars</span><span class="pi">:</span>
<span class="na">user_playlists_for_user</span><span class="pi">:</span> <span class="s2">"</span><span class="s">{{</span><span class="nv"> </span><span class="s">username</span><span class="nv"> </span><span class="s">}}"</span>
<span class="na">user_playlists_state</span><span class="pi">:</span> <span class="s">create</span>
<span class="na">user_playlists_name</span><span class="pi">:</span> <span class="s">Tenacious D</span>
<span class="na">user_playlists_dest_file</span><span class="pi">:</span> <span class="s2">"</span><span class="s">{{</span><span class="nv"> </span><span class="s">temp_playlist_file</span><span class="nv"> </span><span class="s">}}"</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Add tracks to playlist</span>
<span class="na">include_role</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">update_playlists</span>
<span class="na">vars</span><span class="pi">:</span>
<span class="na">update_playlist_state</span><span class="pi">:</span> <span class="s">add</span>
<span class="na">update_playlist_file</span><span class="pi">:</span> <span class="s2">"</span><span class="s">{{</span><span class="nv"> </span><span class="s">temp_playlist_file</span><span class="nv"> </span><span class="s">}}"</span>
<span class="na">update_playlist_track_file</span><span class="pi">:</span> <span class="s2">"</span><span class="s">{{</span><span class="nv"> </span><span class="s">temp_dest_file</span><span class="nv"> </span><span class="s">}}"</span>
</pre></td></tr></tbody></table></code></pre></figure>
<h2 id="add-related-artists-top-tracks-to-a-new-playlist">Add related artists top tracks to a new playlist</h2>
<p>Now let’s do something even more complicated.</p>
<p>Let’s assume we have one artists which music we enjoy. Passing this artists to the role <code class="language-plaintext highlighter-rouge">get_related_artists</code> will give us 20 related artists from the Spotify API. Since we really really enjoy the music from the first given artists the chances might be high, that we will enjoy the music from the related related artists so we may also want to know the related artists from the related artists. Now we will have as a maximum <em>20 * 20 artists</em>. Because we want to do something we want to get the 20 top tracks of each artists and add them to a own playlist. This will give us as maximum <em>20 * (20 * 20)</em> songs in a playlist with a lot of music we may like.</p>
<p>To realise this scenario we need the following Ansible modules:</p>
<ul>
<li>spotify_auth</li>
<li>spotify_auth_create_user_token</li>
<li>spotify_related_artists</li>
<li>spotify_artists_top_tracks</li>
<li>spotify_user_playlists</li>
<li>spotify_update_playlists</li>
</ul>
<p>or the roles:</p>
<ul>
<li>authenticaton</li>
<li>get_related_artists</li>
<li>get_artists_top_tracks</li>
<li>user_playlists</li>
<li>update_playlists</li>
</ul>
<p><strong>The Playbook for using the roles may look like this:</strong></p>
<p>Task 1: Create authentication token</p>
<p>Task 2: Get related artists for artist <em>Young the Giant</em></p>
<p>Task 3: Get related artists for all related artists found for <em>Young the Giant</em></p>
<p>Task 4: Get top tracks for all related artists found</p>
<p>Task 5: Create a new user playlist with the name <em>Ansible created Playlist</em></p>
<p>Task 6: Add all top tracks to the new playlist</p>
<p><img src="https://i.imgur.com/AneW3GK.gif" alt="Create album playlist" /></p>
<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
</pre></td><td class="code"><pre><span class="nn">---</span>
<span class="pi">-</span> <span class="na">hosts</span><span class="pi">:</span> <span class="s">localhost</span>
<span class="na">connection</span><span class="pi">:</span> <span class="s">local</span>
<span class="na">gather_facts</span><span class="pi">:</span> <span class="no">false</span>
<span class="na">vars</span><span class="pi">:</span>
<span class="na">artist</span><span class="pi">:</span> <span class="s">Young the Giant</span>
<span class="na">playlist_name</span><span class="pi">:</span> <span class="s">Ansible created Playlist</span>
<span class="na">temp_dest_file</span><span class="pi">:</span> <span class="s2">"</span><span class="s">/tmp/tempfile.json"</span>
<span class="na">temp_playlist_file</span><span class="pi">:</span> <span class="s2">"</span><span class="s">/tmp/temp_playlist.json"</span>
<span class="na">username</span><span class="pi">:</span> <span class="s">bloch-m</span>
<span class="na">tasks</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Create user authentication token</span>
<span class="na">include_role</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">authentication</span>
<span class="na">vars</span><span class="pi">:</span>
<span class="na">config_file</span><span class="pi">:</span> <span class="s2">"</span><span class="s">{{inventory_dir}}/group_vars/user.yaml"</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Get</span><span class="nv"> </span><span class="s">related</span><span class="nv"> </span><span class="s">artists</span><span class="nv"> </span><span class="s">for</span><span class="nv"> </span><span class="s">artist</span><span class="nv"> </span><span class="s">{{</span><span class="nv"> </span><span class="s">artist</span><span class="nv"> </span><span class="s">}}"</span>
<span class="na">include_role</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">get_related_artists</span>
<span class="na">vars</span><span class="pi">:</span>
<span class="na">get_related_artists_for</span><span class="pi">:</span> <span class="s2">"</span><span class="s">{{</span><span class="nv"> </span><span class="s">artist</span><span class="nv"> </span><span class="s">}}"</span>
<span class="na">get_related_dest_file</span><span class="pi">:</span> <span class="s2">"</span><span class="s">{{</span><span class="nv"> </span><span class="s">temp_dest_file</span><span class="nv"> </span><span class="s">}}"</span>
<span class="na">get_related_artists_output</span><span class="pi">:</span> <span class="s">short</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Get</span><span class="nv"> </span><span class="s">related-related</span><span class="nv"> </span><span class="s">artists</span><span class="nv"> </span><span class="s">for</span><span class="nv"> </span><span class="s">artist</span><span class="nv"> </span><span class="s">{{</span><span class="nv"> </span><span class="s">artist</span><span class="nv"> </span><span class="s">}}"</span>
<span class="na">include_role</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">get_related_artists</span>
<span class="na">vars</span><span class="pi">:</span>
<span class="na">get_related_artists_from_file</span><span class="pi">:</span> <span class="s2">"</span><span class="s">{{</span><span class="nv"> </span><span class="s">temp_dest_file</span><span class="nv"> </span><span class="s">}}"</span>
<span class="na">get_related_dest_file</span><span class="pi">:</span> <span class="s2">"</span><span class="s">{{</span><span class="nv"> </span><span class="s">temp_dest_file</span><span class="nv"> </span><span class="s">}}"</span>
<span class="na">get_related_artists_output</span><span class="pi">:</span> <span class="s">short</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Get</span><span class="nv"> </span><span class="s">top</span><span class="nv"> </span><span class="s">tracks</span><span class="nv"> </span><span class="s">from</span><span class="nv"> </span><span class="s">all</span><span class="nv"> </span><span class="s">found</span><span class="nv"> </span><span class="s">related</span><span class="nv"> </span><span class="s">artists."</span>
<span class="na">include_role</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">get_artists_top_tracks</span>
<span class="na">vars</span><span class="pi">:</span>
<span class="na">get_top_tracks_from_artists_file</span><span class="pi">:</span> <span class="s2">"</span><span class="s">{{</span><span class="nv"> </span><span class="s">temp_dest_file</span><span class="nv"> </span><span class="s">}}"</span>
<span class="na">get_top_tracks_dest_file</span><span class="pi">:</span> <span class="s2">"</span><span class="s">{{</span><span class="nv"> </span><span class="s">temp_dest_file</span><span class="nv"> </span><span class="s">}}"</span>
<span class="na">get_top_tracks_output</span><span class="pi">:</span> <span class="s">short</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Create user playlist</span>
<span class="na">include_role</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">user_playlists</span>
<span class="na">vars</span><span class="pi">:</span>
<span class="na">user_playlists_for_user</span><span class="pi">:</span> <span class="s2">"</span><span class="s">{{</span><span class="nv"> </span><span class="s">username</span><span class="nv"> </span><span class="s">}}"</span>
<span class="na">user_playlists_state</span><span class="pi">:</span> <span class="s">create</span>
<span class="na">user_playlists_name</span><span class="pi">:</span> <span class="s2">"</span><span class="s">{{</span><span class="nv"> </span><span class="s">playlist_name</span><span class="nv"> </span><span class="s">}}"</span>
<span class="na">user_playlists_dest_file</span><span class="pi">:</span> <span class="s2">"</span><span class="s">{{</span><span class="nv"> </span><span class="s">temp_playlist_file</span><span class="nv"> </span><span class="s">}}"</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Add tracks to playlist</span>
<span class="na">include_role</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">update_playlists</span>
<span class="na">vars</span><span class="pi">:</span>
<span class="na">update_playlist_state</span><span class="pi">:</span> <span class="s">add</span>
<span class="na">update_playlist_file</span><span class="pi">:</span> <span class="s2">"</span><span class="s">{{</span><span class="nv"> </span><span class="s">temp_playlist_file</span><span class="nv"> </span><span class="s">}}"</span>
<span class="na">update_playlist_track_file</span><span class="pi">:</span> <span class="s2">"</span><span class="s">{{</span><span class="nv"> </span><span class="s">temp_dest_file</span><span class="nv"> </span><span class="s">}}"</span>
</pre></td></tr></tbody></table></code></pre></figure>
<h2 id="play-a-specific-playlist-on-a-specific-device">Play a specific playlist on a specific device</h2>
<p>Since this is the last example why not celebrating it with a song ?</p>
<p>This example will play a specific song on a specific device. Since we don’t know the <strong>track URI</strong> we need to search for the song. As we neither know the Device ID of the spotify device we query for all playable devices and filter them by name.
Once we have our song and device we will playback our song.</p>
<p>To realise this scenario we need the following Ansible modules:</p>
<ul>
<li>spotify_auth</li>
<li>spotify_user_info</li>
<li>spotify_player</li>
</ul>
<p>or the roles:</p>
<ul>
<li>authenticaton</li>
<li>user_info</li>
<li>player</li>
</ul>
<p><strong>The Playbook for using the roles may look like this:</strong></p>
<p>Task 1: Create authentication token</p>
<p>Task 2: Search for the track <em>Eye of the Tiger</em></p>
<p>Task 3: Find all available spotify devices</p>
<p>Task 4: save device id of a specific spotify device</p>
<p>Task 5: Transfer playback to device</p>
<p>Task 6: set volume on device</p>
<p>Task 7: Pause any current plauback</p>
<p>Task 8: play track found earlier</p>
<p><img src="https://i.imgur.com/NBTETkt.gif" alt="Create album playlist" /></p>
<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
</pre></td><td class="code"><pre><span class="nn">---</span>
<span class="pi">-</span> <span class="na">hosts</span><span class="pi">:</span> <span class="s">localhost</span>
<span class="na">connection</span><span class="pi">:</span> <span class="s">local</span>
<span class="na">gather_facts</span><span class="pi">:</span> <span class="no">false</span>
<span class="na">vars</span><span class="pi">:</span>
<span class="na">username</span><span class="pi">:</span> <span class="s">bloch-m</span>
<span class="na">temp_dest_file</span><span class="pi">:</span> <span class="s2">"</span><span class="s">/tmp/tempfile.json"</span>
<span class="na">tasks</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">include_role</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">authentication</span>
<span class="na">vars</span><span class="pi">:</span>
<span class="na">config_file</span><span class="pi">:</span> <span class="s2">"</span><span class="s">{{inventory_dir}}/group_vars/user.yaml"</span>
<span class="pi">-</span> <span class="na">include_role</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">search</span>
<span class="na">vars</span><span class="pi">:</span>
<span class="na">search_for</span><span class="pi">:</span> <span class="s">tracks</span>
<span class="na">search_for_tracks_name</span><span class="pi">:</span> <span class="s">Eye of the Tiger</span>
<span class="na">search_result_output</span><span class="pi">:</span> <span class="s">short</span>
<span class="na">search_result_limit</span><span class="pi">:</span> <span class="m">1</span>
<span class="na">search_dest_file</span><span class="pi">:</span> <span class="s2">"</span><span class="s">{{</span><span class="nv"> </span><span class="s">temp_dest_file</span><span class="nv"> </span><span class="s">}}"</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Get User information with default settings</span>
<span class="na">spotify_user_info</span><span class="pi">:</span>
<span class="na">auth_token</span><span class="pi">:</span> <span class="s2">"</span><span class="s">{{</span><span class="nv"> </span><span class="s">auth_token</span><span class="nv"> </span><span class="s">}}"</span>
<span class="na">state</span><span class="pi">:</span> <span class="s">devices</span>
<span class="na">output_format</span><span class="pi">:</span> <span class="s">short</span>
<span class="na">register</span><span class="pi">:</span> <span class="s">sp_devices</span>
<span class="pi">-</span> <span class="na">set_fact</span><span class="pi">:</span>
<span class="na">sp_device</span><span class="pi">:</span> <span class="s2">"</span><span class="s">{{</span><span class="nv"> </span><span class="s">item.device_id</span><span class="nv"> </span><span class="s">}}"</span>
<span class="na">with_items</span><span class="pi">:</span> <span class="s2">"</span><span class="s">{{</span><span class="nv"> </span><span class="s">sp_devices.result.devices</span><span class="nv"> </span><span class="s">}}"</span>
<span class="na">when</span><span class="pi">:</span> <span class="s">item.name == "MichaelBloch"</span>
<span class="pi">-</span> <span class="na">include_role</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">player</span>
<span class="na">vars</span><span class="pi">:</span>
<span class="na">spotify_player_state</span><span class="pi">:</span> <span class="s">transfer_playback</span>
<span class="na">spotify_player_device_id</span><span class="pi">:</span> <span class="s2">"</span><span class="s">{{</span><span class="nv"> </span><span class="s">sp_device</span><span class="nv"> </span><span class="s">}}"</span>
<span class="na">register</span><span class="pi">:</span> <span class="s">sp_devices</span>
<span class="pi">-</span> <span class="na">include_role</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">player</span>
<span class="na">vars</span><span class="pi">:</span>
<span class="na">spotify_player_state</span><span class="pi">:</span> <span class="s">volume</span>
<span class="na">volume_level_percent</span><span class="pi">:</span> <span class="m">80</span>
<span class="na">register</span><span class="pi">:</span> <span class="s">sp_devices</span>
<span class="pi">-</span> <span class="na">include_role</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">player</span>
<span class="na">vars</span><span class="pi">:</span>
<span class="na">spotify_player_state</span><span class="pi">:</span> <span class="s">pause</span>
<span class="na">register</span><span class="pi">:</span> <span class="s">sp_devices</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Ansible</span><span class="nv"> </span><span class="s">Spotify</span><span class="nv"> </span><span class="s">Play</span><span class="nv"> </span><span class="s">specific</span><span class="nv"> </span><span class="s">track"</span>
<span class="na">include_role</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">player</span>
<span class="na">vars</span><span class="pi">:</span>
<span class="na">spotify_player_state</span><span class="pi">:</span> <span class="s">play</span>
<span class="na">spotify_track_file</span><span class="pi">:</span> <span class="s2">"</span><span class="s">{{</span><span class="nv"> </span><span class="s">temp_dest_file</span><span class="nv"> </span><span class="s">}}"</span>
<span class="na">register</span><span class="pi">:</span> <span class="s">sp_devices</span>
</pre></td></tr></tbody></table></code></pre></figure>
<p>In the next part I try to introduce a new module to use the Spotify <em>get recommendations based on seeds</em> and combine it with the module <code class="language-plaintext highlighter-rouge">spotify_track_data</code> to explore the wide world of Spotify to find new unexplored songs.</p>
<p>Have fun trying and testing.</p>
<p>Cheers!</p>Michael BlochThe last time I wrote a small introduction about how to use the Spotify authentication modules to create an authentication token and how this authentication token can be consumed by other Spotify Ansible modules e.g. spotify_related_artists (link)[/ansible/2018/04/28/Use-Ansible-to-automate-Spotify-part-I.html]. This time I am going to show you several use cases which combine several modules/roles. Create a playlist for a specific album Add related artists top tracks to a new playlist Play a specific playlist on a specific deviceUse Ansible to automate Spotify - Part I2018-04-28T14:00:00+00:002018-04-28T14:00:00+00:00https://pillpall.github.io/ansible/2018/04/28/Use-Ansible-to-automate-Spotify-part-I<p>To find new music with Spotify I thought about to go a new way … so why not using Ansible to automate Spotify ?</p>
<ul>
<li>The modules</li>
<li>Step 1 - Prerequisites</li>
<li>Step 2 - Authentication</li>
<li>Step 3 - Let’s do something</li>
</ul>
<!--excerpts-->
<h2 id="the-modules">The modules</h2>
<p>To use Ansible to automate Spotify I developed 9 different Ansible modules which contacts the Spotifi API using the Python library Spotipy.</p>
<p>The modules including examples, roles and configuration can be found on Github <a href="https://github.com/PillPall/ansible-spotify-client">(Link)</a>.</p>
<p>Following Ansible modules are at the moment available:</p>
<table>
<thead>
<tr>
<th>Module name</th>
<th>Description</th>
<th>Documentation</th>
</tr>
</thead>
<tbody>
<tr>
<td>spotify_auth</td>
<td>Module for the authentication process</td>
<td><a href="https://github.com/PillPall/ansible-spotify-client/blob/master/docs/Ansible_modules/spotify_auth.md">Link</a></td>
</tr>
<tr>
<td>spotify_auth_create_user_token</td>
<td>Module for the user authentication process</td>
<td><a href="https://github.com/PillPall/ansible-spotify-client/blob/master/docs/Ansible_modules/spotify_auth_create_user_token.md">Link</a></td>
</tr>
<tr>
<td>spotify_album</td>
<td>Get information about one or multiple albums.</td>
<td><a href="https://github.com/PillPall/ansible-spotify-client/blob/master/docs/Ansible_modules/spotify_album.md">Link</a></td>
</tr>
<tr>
<td>spotify_artists_top_tracks</td>
<td>Get the top tracks of an artist</td>
<td><a href="https://github.com/PillPall/ansible-spotify-client/blob/master/docs/Ansible_modules/spotify_artists_top_tracks.md">Link</a></td>
</tr>
<tr>
<td>spotify_player</td>
<td>Spotify Player to play a song and more.</td>
<td><a href="https://github.com/PillPall/ansible-spotify-client/blob/master/docs/Ansible_modules/spotify_player.md">Link</a></td>
</tr>
<tr>
<td>spotify_related_artists</td>
<td>Get related artists</td>
<td><a href="https://github.com/PillPall/ansible-spotify-client/blob/master/docs/Ansible_modules/spotify_related_artists.md">Link</a></td>
</tr>
<tr>
<td>spotify_search</td>
<td>Search in Spotify</td>
<td><a href="https://github.com/PillPall/ansible-spotify-client/blob/master/docs/Ansible_modules/spotify_search.md">Link</a></td>
</tr>
<tr>
<td>spotify_track_data</td>
<td>Get Audio features from a track or analyse tracks</td>
<td><a href="https://github.com/PillPall/ansible-spotify-client/blob/master/docs/Ansible_modules/spotify_artists_top_tracks.md">Link</a></td>
</tr>
<tr>
<td>spotify_user_info</td>
<td>Get user information</td>
<td><a href="https://github.com/PillPall/ansible-spotify-client/blob/master/docs/Ansible_modules/spotify_user_info.md">Link</a></td>
</tr>
<tr>
<td>spotify_user_playlists</td>
<td>Search and create a user playlist</td>
<td><a href="https://github.com/PillPall/ansible-spotify-client/blob/master/docs/Ansible_modules/spotify_user_playlists.md">Link</a></td>
</tr>
<tr>
<td>spotify_update_playlists</td>
<td>Add or remove songs from a playlist</td>
<td><a href="https://github.com/PillPall/ansible-spotify-client/blob/master/docs/Ansible_modules/spotify_update_playlists.md">Link</a></td>
</tr>
</tbody>
</table>
<h2 id="step-1---prerequisites">Step 1 - Prerequisites</h2>
<ul>
<li>
<p><strong>Download or clone the github project</strong></p>
</li>
<li>
<p><strong>Spotify Client ID & Secret</strong></p>
</li>
</ul>
<p>All modules are doing API Calls and to do API calls we need a <strong>CLIENT ID and CLIENT SECRET</strong>. You can go to <a href="https://beta.developer.spotify.com/dashboard/applications">Spotify Developer(Link)</a> and create your own or use the one from my github example. If you create your own one don’t forget to permit a redirect url. I recommend to use the default redirect url <code class="language-plaintext highlighter-rouge">http://mbloch.s3-website-ap-southeast-2.amazonaws.com</code>.</p>
<p>Once you have created your own keys you may want to update the inventory files <code class="language-plaintext highlighter-rouge">ansible/inventory/group_vars/public.yaml</code> & <code class="language-plaintext highlighter-rouge">ansible/inventory/group_vars/user.yaml</code>.</p>
<ul>
<li><strong>Spotipy</strong></li>
</ul>
<p>All modules are using the python library Spotipy version 2.4.4 to connect to the Spotify API. Since the Github version contains several improvements and bug fixes which are not released on pip yet we need to install Spotipy from github:</p>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="o">></span> pip <span class="nb">install </span>git+https://github.com/plamere/spotipy.git <span class="nt">--upgrade</span></code></pre></figure>
<h2 id="step-2---authentication">Step 2 - Authentication</h2>
<p>After finishing the Prerequisites. Let’s see how authentication is done as this is very important and quite complex.</p>
<p>Depending on the authentication process the modules require different parameters.</p>
<p><strong>Public authentication:</strong></p>
<ul>
<li>client_id</li>
<li>client_secret</li>
</ul>
<p><strong>User authentication:</strong></p>
<ul>
<li>client_id</li>
<li>client_secret</li>
<li>username</li>
<li>redirect_uri</li>
<li>scope</li>
</ul>
<p>All Ansible modules require an <strong>authentication_token</strong>. To create an authentication token we need to use the modules <code class="language-plaintext highlighter-rouge">spotify_auth</code> & <code class="language-plaintext highlighter-rouge">spotify_auth_create_user_token</code>.</p>
<p>The <code class="language-plaintext highlighter-rouge">redirect_uri</code> is the URL where Spotify will redirect you once you allow the application access, more information can be found further down at <strong>user authentication process</strong>. The <code class="language-plaintext highlighter-rouge">scope</code> tells Spotify what your application is allows to access. More information and which scopes are available can be found here <a href="https://beta.developer.spotify.com/documentation/general/guides/scopes">(Link)</a>.</p>
<p>Depending on the use case we may want to use the public authentication or user authentication process.</p>
<h4 id="public-authentication-process">Public authentication process</h4>
<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><span class="s">connect to Spotify API</span>
<span class="s">|</span>
<span class="s">v</span>
<span class="s">pass parameters</span>
<span class="s">|</span>
<span class="s">v</span>
<span class="s">receive token</span></code></pre></figure>
<ol>
<li>Connect to Spotify API and passing the parameters <strong>client_id & client_secret</strong>.</li>
<li>Receive <strong>authentication token</strong></li>
</ol>
<p>The public authentication process only requires the module <code class="language-plaintext highlighter-rouge">spotify_auth</code>. The <strong>client_id & client_secret</strong> can be set via configuration file i.e. <code class="language-plaintext highlighter-rouge">ansible/inventory/group_vars/public.yaml</code>:</p>
<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
</pre></td><td class="code"><pre><span class="na">client_id</span><span class="pi">:</span> <span class="s">a430d21d72594499a3aaee8dc9636a3f</span>
<span class="na">client_secret</span><span class="pi">:</span> <span class="s">7660b5c77ed34df48bac67beed2e100c</span>
</pre></td></tr></tbody></table></code></pre></figure>
<p>Playbook use:</p>
<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="code"><pre><span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Spotify public authentication via configuration file</span>
<span class="na">spotify_auth</span><span class="pi">:</span>
<span class="na">config_file</span><span class="pi">:</span> <span class="s2">"</span><span class="s">{{</span><span class="nv"> </span><span class="s">inventory_dir</span><span class="nv"> </span><span class="s">}}/group_vars/public.yaml"</span>
</pre></td></tr></tbody></table></code></pre></figure>
<p>or we can set the parameters in a playbook:</p>
<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="code"><pre><span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Spotify public authentication</span>
<span class="na">spotify_auth</span><span class="pi">:</span>
<span class="na">client_id</span><span class="pi">:</span> <span class="s">a430d21d72594499a3aaee8dc9636a3f</span>
<span class="na">client_secret</span><span class="pi">:</span> <span class="s">7660b5c77ed34df48bac67beed2e100c</span>
</pre></td></tr></tbody></table></code></pre></figure>
<h4 id="user-authentication-process">User authentication process:</h4>
<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><span class="s">Search for cached credentials -> if exist use cached token</span>
<span class="s">|</span>
<span class="s">| if not</span>
<span class="s">|</span>
<span class="s">v</span>
<span class="s">connect to Spotify API</span>
<span class="s">| --------------</span>
<span class="s">v | spotify_auth |</span>
<span class="s">pass parameters --------------</span>
<span class="s">|</span>
<span class="s">v</span>
<span class="s">allow application access &</span>
<span class="s">receive API Code</span>
<span class="s">----------------------------------------------------------------</span>
<span class="s">|</span>
<span class="s">v --------------------------------</span>
<span class="s">pass API Code to | spotify_auth_create_user_token |</span>
<span class="s">Spotify API --------------------------------</span>
<span class="s">|</span>
<span class="s">v</span>
<span class="s">receive token</span></code></pre></figure>
<ol>
<li>
<p>Search for local cached credentials in <code class="language-plaintext highlighter-rouge">/tmp/.cache-USERNAME</code> and use if exist</p>
</li>
<li>
<p>if no cached credentials exists connect to Spotify API and pass parameters</p>
</li>
<li>
<p><strong>Allow application access</strong> for your Spotiy Account & receive API Code</p>
</li>
<li>
<p>Pass <strong>API Code</strong> to Spotify API with module <code class="language-plaintext highlighter-rouge">spotify_auth_create_user_token</code></p>
</li>
<li>
<p>Receive <strong>authentication token</strong></p>
</li>
</ol>
<p>As you can see the user authentication process is a bit more complex and requires the modules <code class="language-plaintext highlighter-rouge">spotify_auth</code> & <code class="language-plaintext highlighter-rouge">spotify_auth_create_user_token</code>. The required parameters can be set via configuration file i.e. <strong>ansible/inventory/group_vars/user.yaml</strong>:</p>
<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="code"><pre><span class="na">client_id</span><span class="pi">:</span> <span class="s">a430d21d72594499a3aaee8dc9636a3f</span>
<span class="na">client_secret</span><span class="pi">:</span> <span class="s">7660b5c77ed34df48bac67beed2e100c</span>
<span class="na">redirect_uri</span><span class="pi">:</span> <span class="s">http://mbloch.s3-website-ap-southeast-2.amazonaws.com</span>
<span class="na">scope</span><span class="pi">:</span> <span class="s">playlist-modify-private,playlist-modify-public,user-read-currently-playing</span>
</pre></td></tr></tbody></table></code></pre></figure>
<p>Playbook use:</p>
<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="code"><pre><span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Spotify user authentication via configuration file</span>
<span class="na">spotify_auth</span><span class="pi">:</span>
<span class="na">username</span><span class="pi">:</span> <span class="s">bloch-m</span>
<span class="na">config_file</span><span class="pi">:</span> <span class="s2">"</span><span class="s">{{</span><span class="nv"> </span><span class="s">inventory_dir</span><span class="nv"> </span><span class="s">}}/group_vars/user.yaml"</span>
</pre></td></tr></tbody></table></code></pre></figure>
<p>or we can set the parameters in a playbook:</p>
<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
</pre></td><td class="code"><pre><span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Spotify user authentication</span>
<span class="na">spotify_auth</span><span class="pi">:</span>
<span class="na">username</span><span class="pi">:</span> <span class="s">bloch-m</span>
<span class="na">client_id</span><span class="pi">:</span> <span class="s">a430d21d72594499a3aaee8dc9636a3f</span>
<span class="na">client_secret</span><span class="pi">:</span> <span class="s">7660b5c77ed34df48bac67beed2e100c</span>
<span class="na">redirect_uri</span><span class="pi">:</span> <span class="s">http://mbloch.s3-website-ap-southeast-2.amazonaws.com</span>
<span class="na">scope</span><span class="pi">:</span> <span class="s">playlist-modify-private,playlist-modify-public,user-read-currently-playing</span>
</pre></td></tr></tbody></table></code></pre></figure>
<p>The module <code class="language-plaintext highlighter-rouge">spotify_auth</code> checks if a cached authentication token exists and will return it. If there is no cached authentication token the module connects to the Spotify API and Ansible will open up your <code class="language-plaintext highlighter-rouge">default OS browser</code> and redirect you to the Spotify authentication page. Now you have to login to Spotify and allow the application Access to your Spotify. Once you have done this you will be redirected to the specified <strong>redirect_uri</strong>. If you are using the default redirect_uri copy the <strong>API Code</strong> as shown. If not you may need to copy the <strong>API Code</strong> from the address bar in your browser i.e. you need to copy the part after <code class="language-plaintext highlighter-rouge">?code=</code> e.g. <code class="language-plaintext highlighter-rouge">https://example.com/?code=ABCDEFG</code> copy <code class="language-plaintext highlighter-rouge">ABCDEFG</code>.</p>
<p>Once copied you need to pass it to the module <code class="language-plaintext highlighter-rouge">spotify_auth_create_user_token</code>.</p>
<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
</pre></td><td class="code"><pre><span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Create user token from Spotify API user code</span>
<span class="na">spotify_auth_create_user_token</span><span class="pi">:</span>
<span class="na">api_user_code</span><span class="pi">:</span> <span class="s2">"</span><span class="s">ABCDEFG"</span>
<span class="na">username</span><span class="pi">:</span> <span class="s">bloch-m</span>
<span class="na">config_file</span><span class="pi">:</span> <span class="s2">"</span><span class="s">{{inventory_dir}}/group_vars/user.yaml"</span>
<span class="s">or</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Create user token from Spotify API user code</span>
<span class="na">spotify_auth_create_user_token</span><span class="pi">:</span>
<span class="na">api_user_code</span><span class="pi">:</span> <span class="s2">"</span><span class="s">ABCDEFG"</span>
<span class="na">username</span><span class="pi">:</span> <span class="s">bloch-m</span>
<span class="na">client_id</span><span class="pi">:</span> <span class="s">a430d21d72594499a3aaee8dc9636a3f</span>
<span class="na">client_secret</span><span class="pi">:</span> <span class="s">7660b5c77ed34df48bac67beed2e100c</span>
<span class="na">redirect_uri</span><span class="pi">:</span> <span class="s">http://mbloch.s3-website-ap-southeast-2.amazonaws.com</span>
<span class="na">scope</span><span class="pi">:</span> <span class="s">playlist-modify-private,playlist-modify-public,user-read-currently-playing</span>
</pre></td></tr></tbody></table></code></pre></figure>
<h4 id="authentication-role">Authentication role</h4>
<p>To automate the authentication process as far as possible I created the role <code class="language-plaintext highlighter-rouge">authentication</code> which will set the authentication token as fact <code class="language-plaintext highlighter-rouge">auth_token</code>.</p>
<p>Role use in playbook:</p>
<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
</pre></td><td class="code"><pre><span class="nn">---</span>
<span class="pi">-</span> <span class="na">hosts</span><span class="pi">:</span> <span class="s">localhost</span>
<span class="na">connection</span><span class="pi">:</span> <span class="s">local</span>
<span class="na">gather_facts</span><span class="pi">:</span> <span class="no">false</span>
<span class="na">vars</span><span class="pi">:</span>
<span class="na">config_file</span><span class="pi">:</span> <span class="s2">"</span><span class="s">{{</span><span class="nv"> </span><span class="s">inventory_dir</span><span class="nv"> </span><span class="s">}}/group_vars/user.yaml"</span>
<span class="na">username</span><span class="pi">:</span> <span class="s">bloch-m</span>
<span class="na">roles</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">authentication</span>
</pre></td></tr></tbody></table></code></pre></figure>
<p>The tricky part is passing the <strong>API Code</strong>, for creating the user authentication token, from the Ansible module <code class="language-plaintext highlighter-rouge">spotify_auth</code> to the module <code class="language-plaintext highlighter-rouge">spotify_auth_create_user_token</code>. To realise this, the role will tell Ansible to wait for Input while the Browser opens up the URL define in the <code class="language-plaintext highlighter-rouge">redirect_uri</code> option. Once the application is allowed copy the <strong>API code</strong> and go back to your terminal where Ansible is waiting for you and enter the <strong>API Code</strong>. After entering the API Code you will get an authentication token to use with the other Ansible modules like <code class="language-plaintext highlighter-rouge">spotify_update_playlists</code>.</p>
<p>Initial user authentication:
<img src="https://i.imgur.com/PZNaGGO.gif" alt="User authentication" /></p>
<p>Cached User authentication:
<img src="https://i.imgur.com/xsAxAWY.gif" alt="User authentication" /></p>
<p>Public authentication:
<img src="https://i.imgur.com/qs4EVdi.gif" alt="Public authentication" /></p>
<h2 id="step-3---lets-do-something">Step 3 - Let’s do something</h2>
<p>Now that we know how to authenticate we can finally do something. Let’s start with something easy…</p>
<h3 id="search-with-ansible">Search with Ansible</h3>
<p>To search in Spotify with Ansible we can use the role <code class="language-plaintext highlighter-rouge">search</code> which is using the module <code class="language-plaintext highlighter-rouge">spotify_search</code>. The module allows us to search for <code class="language-plaintext highlighter-rouge">artists, albums, playlists, tracks, artists & album, artists & track</code>.</p>
<p>Role use in playbook:</p>
<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
</pre></td><td class="code"><pre><span class="nn">---</span>
<span class="pi">-</span> <span class="na">hosts</span><span class="pi">:</span> <span class="s">localhost</span>
<span class="na">connection</span><span class="pi">:</span> <span class="s">local</span>
<span class="na">gather_facts</span><span class="pi">:</span> <span class="no">false</span>
<span class="na">vars</span><span class="pi">:</span>
<span class="na">config_file</span><span class="pi">:</span> <span class="s2">"</span><span class="s">{{</span><span class="nv"> </span><span class="s">inventory_dir</span><span class="nv"> </span><span class="s">}}/group_vars/public.yaml"</span>
<span class="na">search_for</span><span class="pi">:</span> <span class="s">artists</span>
<span class="na">search_for_artists_name</span><span class="pi">:</span> <span class="s">Young the Giant</span>
<span class="na">search_result_output</span><span class="pi">:</span> <span class="s">short</span>
<span class="na">search_result_limit</span><span class="pi">:</span> <span class="m">20</span>
<span class="na">roles</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">authentication</span>
<span class="pi">-</span> <span class="s">search</span>
</pre></td></tr></tbody></table></code></pre></figure>
<p>As you can see we first execute the role <code class="language-plaintext highlighter-rouge">authentication</code> and than executing the role <code class="language-plaintext highlighter-rouge">search</code>. The authentication role takes care of getting an authentication token from the Spotify API and due setting the authentication token as fact <code class="language-plaintext highlighter-rouge">auth_token</code> it can be used by all other roles included in the playbook.</p>
<p>Search artist Young the Giant:
<img src="https://i.imgur.com/xzOt9tf.gif" alt="Search artist Young the Giant" /></p>
<h3 id="access-some-user-data">Access some User data</h3>
<p>We can use the role <code class="language-plaintext highlighter-rouge">user_info</code> which use the module <code class="language-plaintext highlighter-rouge">spotify_user_info</code>. This module allows us to access user data like <code class="language-plaintext highlighter-rouge">current playback, recently played, users top artist, users top tracks and general user info</code>.</p>
<p>Role use in playbook:</p>
<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
</pre></td><td class="code"><pre><span class="nn">---</span>
<span class="pi">-</span> <span class="na">hosts</span><span class="pi">:</span> <span class="s">localhost</span>
<span class="na">connection</span><span class="pi">:</span> <span class="s">local</span>
<span class="na">gather_facts</span><span class="pi">:</span> <span class="no">false</span>
<span class="na">vars</span><span class="pi">:</span>
<span class="na">config_file</span><span class="pi">:</span> <span class="s2">"</span><span class="s">{{inventory_dir}}/group_vars/user.yaml"</span>
<span class="na">username</span><span class="pi">:</span> <span class="s">bloch-m</span>
<span class="na">user_info_output</span><span class="pi">:</span> <span class="s">short</span>
<span class="na">user_info_limit</span><span class="pi">:</span> <span class="m">50</span>
<span class="na">user_info_dest_file</span><span class="pi">:</span> <span class="s2">"</span><span class="s">{{</span><span class="nv"> </span><span class="s">playbook_dir</span><span class="nv"> </span><span class="s">}}/../files/{{</span><span class="nv"> </span><span class="s">username</span><span class="nv"> </span><span class="s">}}_top_tracks.json"</span>
<span class="na">user_info_state</span><span class="pi">:</span> <span class="s">top_tracks</span>
<span class="na">user_info_time_range</span><span class="pi">:</span> <span class="s">medium_term</span>
<span class="na">roles</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">authentication</span>
<span class="pi">-</span> <span class="s">user_info</span>
</pre></td></tr></tbody></table></code></pre></figure>
<p>This playbook will save informations in short format about my most 50 played tracks within the time range <code class="language-plaintext highlighter-rouge">medium_term</code> to <code class="language-plaintext highlighter-rouge">{{ playbook_dir }}/../files/{{ username }}_top_tracks.json</code>. The saved file can than be consumed i.e. from the module <code class="language-plaintext highlighter-rouge">spotify_update_playlists</code> for adding those tracks to a own playlist.</p>
<p>Get users top tracks:
<img src="https://i.imgur.com/LIooDPp.gif" alt="Get users top tracks" /></p>
<h3 id="conclusions">Conclusions</h3>
<p>Since the authentication process is a little bit complex I would recommend to always run the <code class="language-plaintext highlighter-rouge">authentication</code> role first.</p>
<p>Furthermore, I tried to build the modules as much as possible compatible to each other. Which means every module which has the capability to load information from a file can use the save results from other modules e.g. the saved output from the module <code class="language-plaintext highlighter-rouge">spotify_related_artists</code> can be consumed from the module <code class="language-plaintext highlighter-rouge">spotify_artists_top_tracks</code> to get all top tracks from related artists. This gives us the possibility to create more complex Ansible playbooks like add all top tracks from all found related artists to a new created playlist.</p>
<p>To get more information about the project, the Ansible modules and more examples visit the Github project <a href="https://github.com/PillPall/ansible-spotify-client">(Link)</a>.</p>
<p>More about constructing more complex playbooks is coming up in Part II.</p>
<p>Have fun trying and testing.</p>
<p>Cheers!</p>Michael BlochTo find new music with Spotify I thought about to go a new way … so why not using Ansible to automate Spotify ? The modules Step 1 - Prerequisites Step 2 - Authentication Step 3 - Let’s do somethingCreate own startup script for MacOSX Sierra2018-03-18T00:00:00+00:002018-03-18T00:00:00+00:00https://pillpall.github.io/macos/2018/03/18/mac-os-own-startup-script<p>A small description how to create your own Bash script which got’s loaded after login.
<!--excerpts--></p>
<p>I want to automate the start of a Virtual Machine in VirtualBox after I login and additionally to mount a NFS share which got’s exported within my Virtual Machine. Also the opposite when I shutdown/restart my Laptop.</p>
<p>I use my Virtual Machine for several reasons but mainly to develop Software as I don’t like to install all necessary tools and dependencies on my Laptop OS. So, to run all make commands I connect to my VM via SSH and use the NFS Export to manage and edit all files and repos on my Laptop OS.</p>
<p>Since MacOSX Sierra use launchd for starting/stopping daemons, applications, processes etc. we need to create a launchd Property List File. More information about creating Launchd jobs in MacOSX can be taken from the the Apple Developer Documentation <a href="https://developer.apple.com/library/content/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html">here(link)</a>.</p>
<h2 id="launchd-property-list-file">launchd Property List File</h2>
<p>The Property List file contains all information about our script we want to run at startup/shutdown in XML Syntax. As taken from the documentation, following property list keys are required:</p>
<table>
<thead>
<tr>
<th>Key</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Label</strong></td>
<td><em>Contains a unique string to identify your daemon to launchd.</em></td>
</tr>
<tr>
<td><strong>ProgramArguments</strong></td>
<td><em>The arguments used to launch your daemon.</em></td>
</tr>
<tr>
<td> </td>
<td> </td>
</tr>
</tbody>
</table>
<p>For a complete list of all available keys have a look into the man page <code class="language-plaintext highlighter-rouge">man launchd.plist</code>.</p>
<p>My launchd Property List File <code class="language-plaintext highlighter-rouge">com.user.virtualbox.startvm.plist</code>:</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
</pre></td><td class="code"><pre><span class="cp"><?xml version="1.0" encoding="UTF-8"?></span>
<span class="cp"><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"></span>
<span class="nt"><plist</span> <span class="na">version=</span><span class="s">"1.0"</span><span class="nt">></span>
<span class="nt"><dict></span>
<span class="nt"><key></span>Label<span class="nt"></key></span>
<span class="nt"><string></span>com.user.virtualbox.startvm<span class="nt"></string></span>
<span class="nt"><key></span>ProgramArguments<span class="nt"></key></span>
<span class="nt"><array></span>
<span class="nt"><string></span>/usr/local/bin/start_vm.sh<span class="nt"></string></span>
<span class="nt"></array></span>
<span class="nt"><key></span>RunAtLoad<span class="nt"></key></span>
<span class="nt"><true/></span>
<span class="nt"><key></span>LaunchOnlyOnce<span class="nt"></keyu></span>
<span class="nt"><true/></span>
<span class="nt"><key></span>StandardOutPath<span class="nt"></key></span>
<span class="nt"><string></span>/Users/mbloch/log/startvm_stdout.log<span class="nt"></string></span>
<span class="nt"><key></span>StandardErrorPath<span class="nt"></key></span>
<span class="nt"><string></span>/Users/mbloch/log/startvm_errout.log<span class="nt"></string></span>
<span class="nt"></dict></span>
<span class="nt"></plist></span>
</pre></td></tr></tbody></table></code></pre></figure>
<p>Now that Property List file exist we need to consider when we want to run the script.</p>
<table>
<thead>
<tr>
<th>When</th>
<th>From where (Path)</th>
<th>Type</th>
</tr>
</thead>
<tbody>
<tr>
<td>Sytem Startup/ Shutdown</td>
<td>/System/Library/LaunchDaemons</td>
<td>System-wide daemons provided by Mac OS X.</td>
</tr>
<tr>
<td> </td>
<td>/Library/LaunchDaemons</td>
<td>System-wide daemons provided by the administrator.</td>
</tr>
<tr>
<td>Login / Logout</td>
<td>/System/Library/LaunchAgents</td>
<td>Per-user agents provided by Mac OS X.</td>
</tr>
<tr>
<td> </td>
<td>/Library/LaunchAgents</td>
<td>Per-user agents provided by the administrator.</td>
</tr>
<tr>
<td> </td>
<td>~/Library/LaunchAgents</td>
<td>Per-user agents provided by the user</td>
</tr>
<tr>
<td> </td>
<td> </td>
<td> </td>
</tr>
</tbody>
</table>
<p>Since I want to start my script after login and before logout I placed my Property List file under <code class="language-plaintext highlighter-rouge">~/Library/LaunchAgents/com.user.virtualbox.startvm.plist</code>.</p>
<p>Now that we have our Property List file we need to add it to launchd. To avoid errors while adding it to launchd like. If you place it under the User folders <code class="language-plaintext highlighter-rouge">~/Library/LaunchAgents</code> you have to make sure the permissions are set to the user. For the other folders make sure you use <code class="language-plaintext highlighter-rouge">root:wheel</code>.</p>
<p>Run</p>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash">/bin/bash># launchctl load <span class="nt">-w</span> ~/Library/LaunchAgents/com.user.virtualbox.startvm.plist</code></pre></figure>
<p>The <code class="language-plaintext highlighter-rouge">-w</code> option will enable our service directly.</p>
<h2 id="startupshutdown-script">Startup/Shutdown script</h2>
<p>Now that we told our OS about our new Startup/Shutdown script we need to create it.</p>
<p>Basically you can use a simple Hello world script as a startup script. But if you want to make sure it runs also during shutdown/logout you need to run this script continuously, till shutdown/logout.</p>
<p>I realised my script to start/stop the VMs and to mount/umount the NFS share with 6 functions:</p>
<ul>
<li>Start VM</li>
<li>Stop VM</li>
<li>Mount NFS</li>
<li>UMOUNT NFS</li>
<li>Handler</li>
<li>Run in Background</li>
</ul>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
</pre></td><td class="code"><pre>#! /bin/bash
#
# Startup script for MacOSX Sierra to start and stop a Virtual Machine
# everytime this scripts gots executed or receives a SIGTERM SIGKILL SIGINT SIGHUP Signal
#
# Variables
VM_ID=d9782aed-e997-42b1-96f8-1cdb1ee3d55c
Mount_PATH=/Users/mbloch/projects
NFS_IP=192.168.98.101
NFS_PATH=/data/projects
NFS_OPTS=noowners,nolockd,resvport,hard,intr,rw,tcp,nfc
StartVM()
{
echo "Start VM"
/usr/local/bin/VBoxManage startvm ${VM_ID} --type headless
echo ${?}
sleep 10
MountNFS
echo "All done"
Background
}
StopVM()
{
UmountNFS
echo "shutdown VM"
/usr/local/bin/VBoxManage controlvm ${VM_ID} acpipowerbutton
echo ${?}
}
MountNFS()
{
i=0
err_nfs=1
if mount |grep projects > /dev/null; then
echo "Already mounted"
else
while [ ${i} -lt 120 ]; do
if [ ${err_nfs} -gt 0 ]; then
echo "Mount NFS"
sudo /sbin/mount -t nfs -o ${NFS_OPTS} ${NFS_IP}:${NFS_PATH} ${Mount_PATH}
err_nfs=$(echo $?)
echo ${err_nfs}
sleep 1
i=$[$i+1]
else
echo "Mount done"
i=256
fi
done
fi
}
UmountNFS()
{
if mount |grep projects > /dev/null; then
echo "Unmount NFS"
sudo /sbin/umount ${Mount_PATH}
fi
}
Handler()
{
echo $(date)
#
# Condition check if a VM with given VM ID is already running
# Yes stop it, no start it
#
/bin/ps -Af|grep ${VM_ID}|grep -v grep
err=$(echo $?)
if [ ${err} -gt 0 ]; then
StartVM
else
StopVM
fi
}
Background()
{
tail -f /dev/null <span class="err">&</span>
wait $!
}
trap StopVM SIGTERM SIGKILL SIGINT SIGHUP
Handler;
</pre></td></tr></tbody></table></code></pre></figure>
<p>The most important part of this script is the <code class="language-plaintext highlighter-rouge">Background function</code> and the <code class="language-plaintext highlighter-rouge">trap</code> line. With <code class="language-plaintext highlighter-rouge">trap</code> we say call function <code class="language-plaintext highlighter-rouge">StopVM</code> when a System signals like a shutdown/logout event <code class="language-plaintext highlighter-rouge">SIGTERM</code> or a kill event <code class="language-plaintext highlighter-rouge">SIGKILL</code> occurs. But our script can only listen to those events when it’s running. That’s why we run a endless loop in the background function.</p>
<p>Now after every login my VM get started, the NFS share gets mounted and before logout my NFS Share gets umounted and my VM gets shutdown.</p>
<p>My examples can be found <a href="https://github.com/PillPall/examples/tree/master/sierra-launchd-script">here(link)</a>.</p>Michael BlochA small description how to create your own Bash script which got’s loaded after login.How to get a saved (Wifi) Password in MacOSx2018-02-19T23:00:00+00:002018-02-19T23:00:00+00:00https://pillpall.github.io/macos/2018/02/19/MacOSx_get_saved_WiFi_password<p>Short description of how to get a password e.g. Wifi Password in MacOSx.</p>
<ol>
<li>
<p>Open Spotlight Search by clicking on the magnifier at the bottom right of the taskbar or by using the keyboard combination <code class="language-plaintext highlighter-rouge">Command + space</code></p>
</li>
<li>
<p>Search for Keychain Access and open it</p>
</li>
</ol>
<!--excerpts-->
<p><img src="https://pillpall.github.io/assets/posts/macos_keychain/1_search_key_chain.png" alt="Search Keychain" /></p>
<ol>
<li>Type the name of the Wifi you want to know the password from
<img src="https://pillpall.github.io/assets/posts/macos_keychain/2_search_wifi_network.png" alt="Search wifi" /></li>
<li>
<p>double click on the finding</p>
</li>
<li>Click on <code class="language-plaintext highlighter-rouge">show password</code>
<img src="https://pillpall.github.io/assets/posts/macos_keychain/3_see_password.png" alt="Show password" /></li>
<li>
<p>Type in your Password</p>
</li>
<li>You should now see your password</li>
</ol>Michael BlochShort description of how to get a password e.g. Wifi Password in MacOSx. Open Spotlight Search by clicking on the magnifier at the bottom right of the taskbar or by using the keyboard combination Command + space Search for Keychain Access and open itRuby conditions if..else.. and RuboCop guard clause etc. …2018-02-19T22:00:00+00:002018-02-19T22:00:00+00:00https://pillpall.github.io/ruby/2018/02/19/ruby-if-else-guard-clauses<p>Examples of how to make if else clauses in Ruby nicer and avoid rubocop errors like</p>
<p><code class="language-plaintext highlighter-rouge">Favor modifier if usage when having a single-line body. Another good alternative is the usage of control flow &&/||.</code></p>
<p><code class="language-plaintext highlighter-rouge">Use self-assignment shorthand +=.</code></p>
<p><code class="language-plaintext highlighter-rouge">Use a guard clause instead of wrapping the code inside a conditional expression.</code></p>
<!--excerpts-->
<p>More information about making Ruby code looks nicer can be found in the RuboCop docu <a href="http://www.rubydoc.info/gems/rubocop/RuboCop/Cop/Style">(Link)</a></p>
<h3 id="conditionalassignment">ConditionalAssignment</h3>
<p>Rubocop Error:</p>
<p><code class="language-plaintext highlighter-rouge">Style/ConditionalAssignment: Use the return of the conditional for variable assignment and comparison</code></p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
</pre></td><td class="code"><pre><span class="c1"># save return value to variable</span>
<span class="nb">puts</span> <span class="s1">'save return value to variable'</span>
<span class="n">x</span> <span class="o">=</span> <span class="mi">1</span>
<span class="c1"># Bad :-(</span>
<span class="k">if</span> <span class="n">x</span> <span class="o"><=</span> <span class="mi">1</span>
<span class="n">response</span> <span class="o">=</span> <span class="kp">true</span>
<span class="k">else</span>
<span class="n">response</span> <span class="o">=</span> <span class="kp">false</span>
<span class="k">end</span>
<span class="nb">puts</span> <span class="n">response</span>
<span class="c1"># Good :-)</span>
<span class="nb">puts</span> <span class="kp">false</span> <span class="k">unless</span> <span class="n">x</span> <span class="o"><=</span> <span class="mi">1</span>
<span class="c1"># or</span>
<span class="nb">puts</span> <span class="kp">true</span> <span class="k">if</span> <span class="n">x</span> <span class="o"><=</span> <span class="mi">1</span>
</pre></td></tr></tbody></table></code></pre></figure>
<p>output:</p>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="code"><pre><span class="nv">$# </span>rspec test.rb
save <span class="k">return </span>value to variable
<span class="nb">true
true</span>
</pre></td></tr></tbody></table></code></pre></figure>
<h3 id="ifunlessmodifier">IfUnlessModifier</h3>
<p>Rubocop Error:</p>
<p><code class="language-plaintext highlighter-rouge">Style/IfUnlessModifier: Favor modifier if usage when having a single-line body. Another good alternative is the usage of control flow &&/||.</code></p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
</pre></td><td class="code"><pre><span class="c1"># String comparison</span>
<span class="nb">puts</span> <span class="s1">'String comparison'</span>
<span class="n">header</span> <span class="o">=</span> <span class="s1">'HEAD'</span>
<span class="n">head</span> <span class="o">=</span> <span class="s1">'HEAD'</span>
<span class="c1"># Bad :-(</span>
<span class="k">if</span> <span class="n">header</span> <span class="o">==</span> <span class="n">head</span>
<span class="nb">puts</span> <span class="kp">true</span>
<span class="k">end</span>
<span class="c1"># Good :-)</span>
<span class="nb">puts</span> <span class="kp">true</span> <span class="k">if</span> <span class="n">header</span><span class="p">.</span><span class="nf">eql?</span> <span class="n">head</span>
<span class="c1"># or</span>
<span class="nb">puts</span> <span class="kp">false</span> <span class="k">unless</span> <span class="n">header</span><span class="p">.</span><span class="nf">eql?</span> <span class="n">head</span>
</pre></td></tr></tbody></table></code></pre></figure>
<p>output:</p>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="code"><pre><span class="nv">$# </span>rspec test.rb
String comparison
<span class="nb">true
true</span>
</pre></td></tr></tbody></table></code></pre></figure>
<h3 id="selfassignment">SelfAssignment</h3>
<p>Rubocop Error:</p>
<p><code class="language-plaintext highlighter-rouge">Style/SelfAssignment: Use self-assignment shorthand +=.</code></p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
</pre></td><td class="code"><pre><span class="nb">puts</span> <span class="s1">'easy calculation'</span>
<span class="n">i</span> <span class="o">=</span> <span class="mi">1</span>
<span class="c1"># Bad :-(</span>
<span class="n">i</span> <span class="o">=</span> <span class="mi">1</span>
<span class="n">i</span> <span class="o">=</span> <span class="n">i</span> <span class="o">+</span> <span class="mi">1</span>
<span class="nb">puts</span> <span class="n">i</span>
<span class="c1"># Good :-)</span>
<span class="n">i</span> <span class="o">=</span> <span class="mi">1</span>
<span class="n">i</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="nb">puts</span> <span class="n">i</span>
</pre></td></tr></tbody></table></code></pre></figure>
<p>output:</p>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="code"><pre><span class="nv">$# </span>rspec test.rb
easy calculation
2
2
</pre></td></tr></tbody></table></code></pre></figure>
<h3 id="guardclause">GuardClause</h3>
<p>Rubocop Error:</p>
<p><code class="language-plaintext highlighter-rouge">Style/GuardClause: Use a guard clause instead of wrapping the code inside a conditional expression</code></p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
</pre></td><td class="code"><pre><span class="c1"># Bad :-(</span>
<span class="nb">puts</span> <span class="s1">'def my_method'</span>
<span class="k">def</span> <span class="nf">my_method</span>
<span class="n">i</span> <span class="o">=</span> <span class="mi">5</span>
<span class="k">if</span> <span class="n">i</span> <span class="o">></span> <span class="mi">0</span>
<span class="nb">puts</span> <span class="s1">'i greater 0'</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="c1"># Good :-)</span>
<span class="k">def</span> <span class="nf">my_method</span>
<span class="n">i</span> <span class="o">=</span> <span class="mi">5</span>
<span class="nb">puts</span> <span class="s1">'i greater 0'</span> <span class="k">unless</span> <span class="n">i</span> <span class="o"><</span><span class="mi">0</span>
<span class="k">end</span>
</pre></td></tr></tbody></table></code></pre></figure>
<p>output:</p>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="code"><pre><span class="nv">$# </span>rspec test.rb
def my_method
i greater 0
i greater 0
</pre></td></tr></tbody></table></code></pre></figure>
<h3 id="more-complex-example">More complex example</h3>
<p>Here is a more complex example:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
</pre></td><td class="code"><pre><span class="c1"># Calculate errors</span>
<span class="nb">puts</span> <span class="s1">'calculate errors'</span>
<span class="n">i</span> <span class="o">=</span> <span class="mi">0</span>
<span class="n">exit_code</span> <span class="o">=</span> <span class="mi">0</span>
<span class="n">exist</span> <span class="o">=</span> <span class="kp">true</span>
<span class="n">response_code</span> <span class="o">=</span> <span class="mi">2</span>
<span class="n">ii</span> <span class="o">=</span> <span class="mi">5</span>
<span class="c1"># Bad :-(</span>
<span class="k">if</span> <span class="n">exist</span> <span class="o">==</span> <span class="kp">true</span>
<span class="k">while</span> <span class="n">i</span> <span class="o"><</span> <span class="n">ii</span>
<span class="k">if</span> <span class="n">response_code</span> <span class="o">==</span> <span class="mi">2</span>
<span class="n">exit_code</span> <span class="o">=</span> <span class="n">exit_code</span> <span class="o">+</span> <span class="mi">1</span>
<span class="k">end</span>
<span class="n">i</span> <span class="o">=</span> <span class="n">i</span> <span class="o">+</span> <span class="mi">1</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">if</span> <span class="n">exit_code</span> <span class="o"><</span> <span class="mi">0</span>
<span class="nb">puts</span> <span class="kp">false</span>
<span class="k">else</span>
<span class="nb">puts</span> <span class="kp">true</span>
<span class="k">end</span>
<span class="c1"># Good :-)</span>
<span class="k">if</span> <span class="n">exist</span><span class="p">.</span><span class="nf">eql?</span> <span class="kp">true</span>
<span class="k">while</span> <span class="n">i</span> <span class="o"><</span> <span class="n">ii</span>
<span class="n">exit_code</span> <span class="o">+=</span> <span class="mi">1</span> <span class="k">unless</span> <span class="n">response_code</span><span class="p">.</span><span class="nf">eql?</span> <span class="mi">0</span>
<span class="n">i</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="nb">puts</span> <span class="kp">true</span> <span class="k">unless</span> <span class="n">exit_code</span> <span class="o">></span> <span class="mi">0</span>
<span class="c1"># or</span>
<span class="nb">puts</span> <span class="kp">true</span> <span class="k">if</span> <span class="n">exit_code</span><span class="p">.</span><span class="nf">eql?</span> <span class="mi">0</span>
<span class="c1"># or</span>
<span class="nb">puts</span> <span class="kp">false</span> <span class="k">unless</span> <span class="n">exit_code</span><span class="p">.</span><span class="nf">eql?</span> <span class="mi">0</span>
</pre></td></tr></tbody></table></code></pre></figure>
<p>output:</p>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="code"><pre><span class="nv">$# </span>rspec test.rb
calculate errors
<span class="nb">true
false</span>
</pre></td></tr></tbody></table></code></pre></figure>
<p>As you can see, you can massively reduce your code.<a href="https://github.com/PillPall/examples/blob/master/ruby_rubocop/test.rb">(Source Code)</a></p>Michael BlochExamples of how to make if else clauses in Ruby nicer and avoid rubocop errors like Favor modifier if usage when having a single-line body. Another good alternative is the usage of control flow &&/||. Use self-assignment shorthand +=. Use a guard clause instead of wrapping the code inside a conditional expression.